本教程操作环境:windows7系统、vue2.9.6版,DELL G3电脑。
Vue 组件化开发
什么叫做组件化
vue.js 有两大法宝,一个是数据驱动,另一个就是组件化,那么问题来了,什么叫做组件化,为什么要组件化?接下来我就针对这两个问题一一解答,所谓组件化,就是把页面拆分成多个组件,每个组件依赖的 CSS、JS、模板、图片等资源放在一起开发和维护。 因为组件是资源独立的,所以组件在系统内部可复用,组件和组件之间可以嵌套,如果项目比较复杂,可以极大简化代码量,并且对后期的需求变更和维护也更加友好。
1、组件化开发指的是将复杂的业务拆分为一个又一个的组件
2、组件化开发的组件一般来说要灵活
3、组件化开发涉及到了Vue的js组件封装,需要掌握Vue基础、Vue实例方法与属性、Vue.extend、Vue插件等知识
如何进行组件化开发
先看下图:
这是 vue.js 中的一个报错,原因是使用了一个未经注册的组件 lx-xxx
,这个报错告诉我们一个道理:使用自定义组件之前必须注册。
那么如何注册一个组件呢? Vue.js 提供了 2 种组件的注册方式,全局注册和局部注册。
1. 全局注册
在 vue.js 中我们可以使用 Vue.componenttagName, options) 进行全局注册,例如
Vue.component'my-component', { // 选项 })
2. 局部注册
Vue.js 也同样支持局部注册,我们可以在一个组件内部使用 components 选项做组件的局部注册,例如:
import HelloWorld from './components/HelloWorld' export default { components: { HelloWorld } }
区别:全局组件是挂载在 Vue.options.components
下,而局部组件是挂载在 vm.$options.components
下,这也是全局注册的组件能被任意使用的原因。
组件化开发必备知识
所谓工欲善其事,必先利其器,在正式开发一个组件之前,我们先要掌握一些必备的知识,这里我只会简单介绍一下,详情参考官网。
name
组件的名称,必填
<lx-niu/> <lx-niu></lx-niu/> name: 'lxNiu'
js 中使用驼峰式命令,HTML 使用kebab-case命名。
props
组件属性,用于父子组件通信,可通过this.msg访问
<div>{{msg}}</div> props: { msg: { type: String, default: '' } } show: Boolean // 默认false msg: [String, Boolean] // 多种类型
computed
处理data或者props中的属性,并返回一个新属性
<div>{{newMsg}}</div> computed: { newMsg) { return 'hello ' + this.msg } },
注:因为props,data和computed在编译阶段都会作为vm的属性合并,所以不可重名
render
用render函数描述template
<lx-niu tag='button'>hello world</lx-niu> <script type="text/javascript"> export default { name: 'lxNiu', props: { tag: { type: String, default: 'div' }, }, // h: createElement renderh) { return hthis.tag, {class: 'demo'}, this.$slots.default) } } </script>
render 中的 h 其实就是 createElement,它接受三个参数,返回一个 vnode
h 参数解释:
args1: {string | Function | Object} 用于提供DOM的html内容
args2: {Object} 设置DOM样式、属性、绑定事件之类
args3: {array} 用于设置分发的内容
注:vue编译顺序: template–> compile –> render –> vnode –> patch –> DOM
slot
<lx-niu> <div slot='header'>header</div> <div class="body" slot='body'> <input type="text"> </div> <div slot='footer'>footer</div> <button class='btn'>button</button> </lx-niu> <template> <div> <slot name='header'></slot> <slot name='body'></slot> <slot name='footer'></slot> <slot></slot> </div> </template> <script> export default { name: 'lxNiu', mounted) { this.$slots.header // 包含了slot="foo"的内容 this.$slots.default // 得到一个vnode,没有被包含在具名插槽中的节点,这里是button } } </script>
class
定义子组件的类名
// 父组件 <lx-niu round type='big'/> // 子组件 <div :class="[ type ? 'lx-niu__' + type : '', {'is-round': round}, ]">控制</div> //真实DOM <div class='lx-niu__big is-round'>hello</div>
style
向子组件传递样式
// 父组件 <lx-niu :bodyStyle='{color: "red"}'/> // 子组件 <template> <div :style='bodyStyle'>hello world</div> </template> <script> export default { name: 'lxNiu', props: { bodyStyle: {}, }, } </script>
其他属性
$attrs
v-bind="$attrs" 将除class和style外的属性添加到父组件上,如定义input:
<input v-bind="$attrs">
v-once
组件只渲染一次,后面即使数据发生变化也不会重新渲染,比如例子中val不会变成456
<template> <div> <button @click="show = !show">button</button> <button @click="val = '456'">button</button> <div v-once v-if="show"> <span>{{val}}</span> </div> </div> </template> <script> export default { data) { return{ show: false, val: '123' } }, }; </script>
mixins
// mixin.js export default { data) { return{ msg: 'hello world' } }, methods: { clickBtn) { console.logthis.msg) } }, } // index.vue <button @click="clickBtn">button</button> import actionMixin from "./mixin.js"; export default { methods: {}, mixins: [actionMixin] }
实例演示
比如我们要注册一个 lx-button 这样一个组件,那么目录和伪代码如下:
index.vue
<template> <button>lxButton</button> </template> <script> export default { name: 'lxButton' } </script>
index.js
import lxButton from './src/index' lxButton.install = Vue) => { Vue.componentlxButton.name, lxButton) } export default lxButton
其中 install
是 Vue.js 提供了一个公开方法,这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。MyPlugin.install = function Vue, options){}
watch-弹窗实现原理
<button @click="dialogVisible = true">显示</button> <lx-niu :visible.sync="dialogVisible"></lx-niu> <script> export default { data) { return { dialogVisible: false } }, watch: { dialogVisibleval) { console.log'father change', val) } } } </script>
定义组件
<template> <div v-show="visible"> <button @click="hide">关闭</button> </div> </template> <script> export default { name: 'lxNiu', props: { visible: Boolean }, watch: { visibleval) { console.log'child change:', val) } }, methods: { hide) { this.$emit'update:visible', false); } }, } </script>
点击父组件中的显示
按钮,改变传入子组件中的值,点击子组件中的关闭
,改变父组件中值。
注:@click=“dialogVisible = true” 点击时将dialogVisible的值改为true
注::visible.sync: 双向数据绑定,配合update:visible使用,实现子组件修改父组件中的值
col组件实例
export default { name: 'ElCol', props: { span: { type: Number, default: 24 }, tag: { type: String, default: 'div' }, offset: Number, pull: Number, push: Number, xs: [Number, Object], sm: [Number, Object], md: [Number, Object], lg: [Number, Object], xl: [Number, Object] }, computed: { gutter) { let parent = this.$parent; while parent && parent.$options.componentName !== 'ElRow') { parent = parent.$parent; } return parent ? parent.gutter : 0; } }, renderh) { let classList = []; let style = {}; if this.gutter) { style.paddingLeft = this.gutter / 2 + 'px'; style.paddingRight = style.paddingLeft; } ['span', 'offset', 'pull', 'push'].forEachprop => { if this[prop] || this[prop] === 0) { classList.push prop !== 'span' ? `el-col-${prop}-${this[prop]}` : `el-col-${this[prop]}` ); } }); ['xs', 'sm', 'md', 'lg', 'xl'].forEachsize => { if typeof this[size] === 'number') { classList.push`el-col-${size}-${this[size]}`); } else if typeof this[size] === 'object') { let props = this[size]; Object.keysprops).forEachprop => { classList.push prop !== 'span' ? `el-col-${size}-${prop}-${props[prop]}` : `el-col-${size}-${props[prop]}` ); }); } }); return hthis.tag, { class: ['el-col', classList], style }, this.$slots.default); } };
col组件使用render函数,而不是template来实现组件,原因有两个:
-
该组件有大量的类判断,如果采用template代码比较冗余,使用js代码更加简洁
-
直接render描述性能更好
button组件实例
<template> <button class="el-button" @click="handleClick" :disabled="buttonDisabled || loading" :autofocus="autofocus" :type="nativeType" :class="[ type ? 'el-button--' + type : '', buttonSize ? 'el-button--' + buttonSize : '', { 'is-disabled': buttonDisabled, 'is-loading': loading, 'is-plain': plain, 'is-round': round, 'is-circle': circle } ]" > <i class="el-icon-loading" v-if="loading"></i> <i :class="icon" v-if="icon && !loading"></i> <span v-if="$slots.default"><slot></slot></span> </button> </template> <script> export default { name: 'ElButton', inject: { elForm: { default: '' }, elFormItem: { default: '' } }, props: { type: { type: String, default: 'default' }, size: String, icon: { type: String, default: '' }, nativeType: { type: String, default: 'button' }, loading: Boolean, disabled: Boolean, plain: Boolean, autofocus: Boolean, round: Boolean, circle: Boolean }, computed: { _elFormItemSize) { return this.elFormItem || {}).elFormItemSize; }, buttonSize) { return this.size || this._elFormItemSize || this.$ELEMENT || {}).size; }, buttonDisabled) { return this.disabled || this.elForm || {}).disabled; } }, methods: { handleClickevt) { this.$emit'click', evt); } } }; </script>
局部组件实例
<template> <div class="login"> <login-header /> <login-request /> <login-footer /> </div> </template> <script> import loginHeader from './login-header'; import loginRequest from './login-request'; import loginFooter from './login-footer'; export default { components: { [loginHeader.name]: loginHeader, [loginRequest.name]: loginRequest, [loginFooter.name]: loginFooter } }; </script>
8. 分享总结
- 首先介绍了什么是组件化开发,以及为什么要进行组件化
- 其次介绍了组件开发的两种方式和适用场景,以及进行组件化开发的必备知识
- 最后实例演示了全局组件和局部组件的开发
【相关推荐:《vue.js教程》】
以上就是vue组件化开发是什么意思的详细内容,更多请关注风君子博客其它相关文章!