为了保证组件的复用性,组件中的 data 必须是一个函数。
组件内部定义需要是单个根元素。
9-1.全局注册
全局注册,适用于注册全局组件。
注册一次,全局可用。
Vue.component('ComponentA', {
template: '',
data () {
return {}
},
methods: {}
})TIP
全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生。
自动化全局注册
在现实的工程化开发中,人工手动全局注册一个个的组件,显得非常麻烦,代码也会非常冗余。
假设有以下目录:
.
├── components
│ ├── BaseButton.vue
│ ├── index.js
│ └── table
│ └── BaseTable.vue以 webpack 中的 require.context 为例:
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
// 获取该文件的执行上下文 返回值是一个函数
const context = require.context('../components', true, /\.vue$/)
// 可利用属性keys获取该上下文的符合条件的文件名数组集合
context.keys().forEach(filePath => {
// 根据文件路径获取大写组件名 譬如 ./table/BaseTable.vue 的结果是 BaseTable
const componentName = upperFirst(camelCase(filePath.spilit('/'.pop().replace(/\.\w+$/, ''))))
// 根据文件路径获取组件相关信息
const componentInfo = context(filePath)
// 全局注册
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentInfo.default || componentInfo
)
})9-2.局部注册
局部注册相对于全局注册的一个优势,在使用形如 webpack 的构建系统时,不会打包冗余代码,有利于 tree-shaking。
import ComponentA from 'path/to/ComponentA'
const ComponentB = {
components: {
ComponentA
},
data () {
return {}
},
methods: {}
}9-3.prop
- 单向数据流
如果改变了 prop,会报错:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
TIP
注意,这里的改变 prop 指的是改变了 prop 的指向。
如果 prop 是对象 object,那么改变其属性时不会触发报错(并没有改变其地址指向),直接改变其指向才会报错。
但在子组件中改变对象类型的 prop 属性将会影响到父组件的状态。
因为基本数据类型的映射是值,复杂数据类型的映射是引用。
非 prop 的 attribute 默认会作用于子组件的根元素上,并且会覆盖该根元素上设置的属性(class、style 较为特殊,这俩会重新组合属性)。
- 禁用
Attribute继承
设置 inheritAttrs: false,子组件的根元素将不会继承父组件内设置的 attribute。
注意 inheritAttrs: false 选项不会影响 style 和 class 的绑定。
可利用 inheritAttrs: false 结合 v-bind="$attrs",指定子组件内的目标元素接收父组件内设置的 attribute。(同样,这里 style 和 class 的绑定也不会受影响)
9-4.自定义事件
v-model 默认等于 v-bind:value 及 v-on:input="value = $event.target.value"。
所以 v-model 会改变 value 属性的值。
在单选框 radio 或者复选框 checkbox 上,v-model 如果绑定的是基本类型的值,则会将 value 修改为 true 或 false。
在某些表单下(譬如 checkbox), value 属性是可以结合 name 属性通过 form 表单提交到服务端的,但 value 不能是 true 或 false,即使在 DOM 中布尔值被认作字符串。
通过上述论述,可知在某些场景下,表单元素需要 value 属性进行其他操作时,此时我们不能粗暴的直接使用 v-model 进行绑定,Vue 对此提供了 modle 属性供我们自定义 v-model 绑定。
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})$listeners
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,
// 或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})9-5.插槽
在子组件中:
使用 <slot></slot> 预留默认插槽。
使用 <slot name="name"></slot> 预留具名插槽。
使用 <slot name="name" :prop="prop"></slot> 来预留具名作用域插槽。
在父组件中
- 新语法
自 2.6.0 起的新版本语法,提供了 v-slot,可缩写为 #。
推荐在所有情况下都用在 <template> 标签上。
默认插槽,v-slot 或 v-slot:default
具名插槽,v-slot:[name]
作用域插槽,v-slot:[name]="slotProps"
- 旧语法
旧语法,是 slot 与 slot-scope。
默认插槽,直接填充 或 slot="default"。
具名插槽,slot="name"
作用域插槽,slot="name" slot-scope="slotProps"
- 编译作用域
TIP
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
9-6.动态组件&异步组件
- 动态组件
动态组件可利用 is 属性进行切换。
组件会重新渲染。
① 利用 元素的 is 属性
② is 属性应该用 v-bind 修饰(可以简写为 : )
③ is 属性应该传入注册的组件名
如果想要动态组件保留状态,可以使用 <keep-alive> 来缓存。
TIP
条件渲染 v-if 与动态组件 :is 的区别:
条件渲染
- 工作方式:根据
v-if和v-else-if的条件,Vue会决定渲染TextComponent、ImageComponent、或JsonComponent。 - 优缺点:每个组件的渲染条件都是独立的。如果有多个条件判断(比如更多的组件类型),代码会变得较冗长。
- 性能:条件渲染会根据条件判断只创建一个满足条件的组件,但每次重新渲染时,
Vue可能会销毁和重建组件。
- 工作方式:根据
动态组件
- 工作方式:通过
:is绑定动态指定的组件名称,Vue自动渲染与 item 变量相对应的组件,比如TextComponent、ImageComponent或JsonComponent。 - 优缺点:代码更加简洁,尤其是当有很多种可能的组件类型时,无需为每种类型写条件判断。
- 性能:动态组件会复用现有组件实例,减少频繁的销毁和重建。动态组件的显示和隐藏通常不会销毁组件实例(除非设置
key强制重新渲染)。
- 工作方式:通过
- 异步组件
异步组件根据 resolve 和 reject 的执行情况,决定组件的渲染。
支持以下两种用法:
components: {
'my-component': () => import('./my-async-component')
}
// vue@2.3.0+,vue-router@2.4.0+
components: {
'my-component': () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
}Webpack 提供了code-splitting来将 import() 语法的相关 chunks 自动抽离为 Async chunks。
另外,Webpack 还会提供 Prefetch 和 Preload 来优化异步组件的加载。
异步组件整体执行逻辑如下:
在打包时,
Webpack会根据import()语法,将相关chunks抽离为Async chunks。当
Vue的异步路由被触发时,webpack_require会被调用,利用JSONP来获取Async chunks。当
Async chunks加载成功后,会调用resolve方法,Vue会根据组件名称进行渲染。如果加载失败,会调用
reject方法,Vue会根据组件名称进行渲染。