一、概述
组件化是长期开发过程中一个提炼精华的过程,目的主要是提高复用性、解耦、提升开发效率。本次主要以Vue.js为例来讲解前端组件开发的注意事项,并且带领大家封装自己的组件并发布到npm。
二、环境
- window10 x64
- node v10.16.3
- npm v6.13.4
- yarn
三、什么叫做组件化
Vue.js 有两大特性,一个是数据驱动,另一个就是组件化。接下来我就针对这两个问题一一解答,所谓组件化,就是把页面拆分成多个组件,每个组件依赖的 CSS、JS、模板、图片等资源放在一起开发和维护。 因为组件是资源独立的,所以组件在系统内部可复用,组件和组件之间可以嵌套,如果项目比较复杂,可以极大简化代码量,并且对后期的需求变更和维护也更加友好。
四、如何进行组件化开发
Vue.js 提供了 2 种组件的注册方式,全局注册和局部注册。
4.1 全局注册
在vue.js中我们可以使用Vue.component(tagName,options)
进行全局注册。 例如:
Vue.component('my-component', { // 选项 })
4.2 局部注册
Vue.js 也同样支持局部注册,我们可以在一个组件内部使用 components 选项做组件的局部注册。 例如:
import HelloWorld from './components/HelloWorld' export default { components: { HelloWorld } }
区别:全局组件是挂载在 Vue.options.components 下,而局部组件是挂载在 vm.$options.components 下,这也是全局注册的组件能被任意使用的原因。
五、组件开发的要素
- 组件的名称
- 控制参数
- 自定义内容
- 自定义事件
六、封装一个Vue组件
6.1 开发vue组件要素
6.6.1 组件的名称name(必填)
js 中使用驼峰式命令,HTML 使用kebab-case命名。
<my-component/> <my-component></my-component> …… <script> export default { name: 'MyComponent', props: { prefixCls: { type: String, default: 'ant-editor-wang' }, // eslint-disable-next-line value: { type: String } } …… </script>
6.6.2 props参数
组件属性,用于父子组件通信,可通过this.msg访问。
父对子传参,就需要用到 props,通常的 props 是这样的:
props:['data','type']
// show: Boolean // 默认false //msg: [String, Boolean] // 多种类型
但是通用组件的的应用场景比较复杂,对 props 传递的参数应该添加一些验证规则,常用格式如下:
props: { // 基础类型检测 (`null` 意思是任何类型都可以) propA: Number, // 多种类型 propB: [String, Number], // 必传且是字符串 propC: { type: String, required: true }, // 数字,有默认值 propD: { type: Number, default: 100 }, // 数组/对象的默认值应当由一个工厂函数返回 propE: { type: Object, default: function () { return { message: 'hello' } } }, // 自定义验证函数 propF: { validator: function (value) { return value > 10 } } }
对于通过 props 传入的参数,不建议对其进行操作,因为会同时修改父组件里面的数据 // vue2.5已经针对 props 做出优化,这个问题已经不存在了 如果一定需要有这样的操作,可以这么写:
let copyData = JSON.parse(JSON.stringify(this.data))
6.6.3 computed:
处理data或者props中的属性,并返回一个新属性。(注:因为props,data和computed在编译阶段都会作为vm的属性合并,所以不可重名)
<template> <div>{{newMsg}}</div> </template> <script> export default { computed: { newMsg() { return '前端微服务 ' + this.msg } }, …… } </script>
6.6.4 render
用render函数描述template。
<my-component tag='button'>前端微服务</my-component> <script type="text/javascript"> export default { name: 'MyComponent', props: { tag: { type: String, default: 'div' }, }, // h: createElement render(h) { return h(this.tag, {class: 'demo'}, this.$slots.default) } } </script>
注意: render 中的 h 其实就是 createElement,它接受三个参数,返回一个 vnode
h 参数解释:
- args1:stringFunction | Object用于提供DOM的html内容
- args2: {Object} 设置DOM样式、属性、绑定事件之类
- args3: {array} 用于设置分发的内容
注:vue编译顺序: template–> compile --> render --> vnode --> patch --> DOM
6.6.5 slot定制插槽
分发内容,有传入时显示,无传入 DOM 时显示默认,分为无名和具名两种,this.slots.default默认指向无名插槽,多个slot时用法this.slots.name,一个通用组件,往往不能够完美的适应所有应用场景 所以在封装组件的时候,只需要完成组件 80% 的功能,剩下的 20% 让父组件通过 solt 解决。
// 子组件 <div class="child-btn"> <!-- 具名插槽 --> <slot name="button"></slot> <!-- 匿名插槽(每个组件只能有一个) --> <slot><slot> </div> // 父组件 <child> <!-- 对应子组件中button的插槽 --> <button slot="button">slot按钮</button> </child>
组件封装
<my-component> <div slot='header'>header</div> <div class="body" slot='body'> <input type="text"> </div> <div slot='footer'>footer</div> <button class='btn'>button</button> </my-component> <template> <div> <slot name='header'></slot> <slot name='body'></slot> <slot name='footer'></slot> <slot></slot> </div> </template> <script> export default { name: 'MyComponent', mounted() { this.$slots.header // 包含了slot="foo"的内容 this.$slots.default // 得到一个vnode,没有被包含在具名插槽中的节点,这里是button } } </script>
6.6.6 定义子组件的类名class
// 父组件 <my-component round type='big'/> // 子组件 <div :class="[ type ? 'my-component__' + type : '', {'is-round': round}, ]">控制</div> //真实DOM <div class='my-component__big is-round'>hello</div>
6.6.7 向子组件传递样式 style
// 父组件 <my-component :bodyStyle='{color: "red"}'/> // 子组件 <template> <div :style='bodyStyle'>hello world</div> </template> <script> export default { name: 'MyComponent', props: { bodyStyle: {}, }, } </script>
6.2 其他属性
6.2.1 $attrs
v-bind="$attrs" 将除class和style外的属性添加到父组件上,如定义input:
<input v-bind="$attrs">
6.2.2 v-once
组件只渲染一次,后面即使数据发生变化也不会重新渲染。比如例子中val不会变成2020
<template> <div> <button @click="show = !show">button</button> <button @click="val = '2020'">button</button> <div v-once v-if="show"> <span>{{val}}</span> </div> </div> </template> <script> export default { data() { return{ show: false, val: '123' } }, }; </script>
6.2.3 mixins
// mixin.js export default { data() { return{ msg: 'hello world' } }, methods: { clickBtn() { console.log(this.msg) } }, } // index.vue <button @click="clickBtn">button</button> import actionMixin from "./mixin.js"; export default { methods: {}, mixins: [actionMixin] }
6.3 封装button组件
index.vue
<template> <button>MyButton</button> </template> <script> export default { name: 'MyButton' } </script>
index.js
import MyButton from './src/index' MyButton.install = (Vue) => { Vue.component(MyButton.name, MyButton) } export default MyButton
其中 install
是 Vue.js 提供了一个公开方法,这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。MyPlugin.install = function (Vue, options){}
七、发布到npm
7.1 登陆npm
$ npm login
7.2 发布
$ npm publish