为了保证组件的复用性,组件中的 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
会根据组件名称进行渲染。