又开新坑了,最近重看了vue文档,将之前比较模糊的概念重新理解了一遍,并参考了网上的资料,记一哈加深印象;
vm.attrs与vm.listeners实现组件通信
被$attrs”收容”的非prop特性
$attrs官方文档介绍如下:
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件——在创建高级别的组件时非常有用。
这个属性主要配合非Prop的特性使用,
因为在实际开发中,组件开发者并不知道组件会被传入什么样的特性,所以组件能够接受任意的特性,而这些特性都会被绑定在组件的根元素下;1
<my-component data_1="data1" data_2="data2"></my-component>
此时实例的$attrs
属性将会包含传入的特性名与特性值:1
{ data_1: 'data', data_2: 'data' }
上面说的都比较难懂,简单来说:
- 父组件会向子组件传入各种props;
- 默认情况(
inheritAttrs = true
)下子组件中会接收这些props,但只有在props
属性定义中的会被继承,而其它未定义属性将会传入$attrs
属性中,并且绑定在子组件的根元素上;官方解释:默认情况下父作用域的不被认作 props 的特性绑定 (attribute bindings) 将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上
1 | // Fater.vue |
1 | // Child-component.vue |
可以通过在子组件中设置inheritAttr = false
来取消这个默认行为,这样父组件传入的非Prop特性
便不会被绑定在根元素上;这样便能将组件当作一个普通的HTML标签使用,而无需担心传入的特性会对根元素产生污染;
总之,$attrs
配合inheritAttr = false
使用,能够更好地决定将特性赋予哪个元素;在撰写基础组件时经常用到;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 基础组件
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
v-bind:placeholder="$attrs.placeholder"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
<span>{{ $attrs.span-word }}</span>
</label>
`
})
1 | <base-input placeholder="Hi" span-word="Have a nice day"></base-input> |
深层组件的Prop传递
当需要向深层组件传递Prop时,使用v-bind:"$attrs"
便能将接收到的父组件Prop传递给孙子组件;
父组件1
2
3
4<!-- Fathter.vue -->
<template>
<fater prop_child="1" prop_grand_son="2"></father>
</template>
子组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<!-- Child.vue -->
<template>
<div>
<!-- 将不属于自己的prop传递给孙子组件 -->
<grandson v-bind="$attrs"></grandson>
</div>
</template>
<script>
...
export default {
props: ['prop_child'],
// 必须设置
inheritAttr: false;
components {
grandson
}
...
}
孙子组件1
2
3
4
5
6
7
8
9
10
11<!-- GrandSon.vue -->
<template>
<div>
{{ prop_grandson }}
</div>
</template>
<script>
...
export default {
props: ['prop_grandson'],
}
利用非Prop的绑定特性实现事件代理
对于列表的渲染,如果每个item都添加一个click事件,不仅麻烦还可能导致性能上的问题;此时一般通过利用事件的冒泡机制,在父DOM元素上实现处理事件,实现事件代理;
对于未封装的子元素,可以添加id或class进行识别,但对于封装的组件,不能直接识别;一种方法是向子组件传递Prop,并在事件冒泡时带着这个Prop前来;此时,可以使用HTML提供的data
自定义属性配合vue的非Prop特性绑定,添加不同的事件处理;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<template>
<div id="father-proxy" @click="proxyMethod">
<!-- data-child属性将会被绑定在子组件的根元素上 -->
<list-item data-child="type-A"></list-item>
<list-item data-child="type-B"></list-item>
</div>
<template>
<script>
...
method: {
proxyMethod (e) {
// dataset获取元素所有'data-xxx'属性集对象
if (e.target.dataset.child === 'type-A') {
// Do something
} else if (e.target.dataset.child === 'type-B') {
// Do anotherthing
}
}
}
</script>
$listeners与组件通信
高层组件中,如何向下传递数据已经解决了,利用$attrs
像传送带一般向下传送Prop即可;那么如何向上请求数据改变,即实现数据的双向绑定呢?这时可以使用$listeners
,这是一个包裹了监听事件的对象:使用其中的方法就如同$emit('event', payload)
一般:1
{ focus () { /* ... */}, hello () { /* ... */ } }
$listeners官方文档如下:
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件——在创建更高层次的组件时非常有用。
$listeners
最初是为了解决.native
也解决不了的原生元素的事件触发:1
2
3
4
5<!-- Parent.vue -->
<template>
<!-- 往子组件上绑定事件 -->
<child @focus.native="focus_event" v-model="value"></child>
</template>
1 | <!-- Child.vue --> |
此时,父级的·native
将静默失败,因为子组件并不是一个可focus
的对象,自然不能触发focus
事件;此时可以通过v-on:$listeners
将所有的事件监听器指向特定的子元素,对于input
这种需要配合v-model
工作的元素,可以采用计算属性:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34<template>
<div id="恶人">
{{ label }}
<input
v-bind="$attrs"
v-on="computed_listeners"
:value="value" >
</input>
</div>
</template>
<script>
export default {
data () {
props: ['label', 'value'],
}
inheritAttr: false,
computed: {
computed_listeners () {
const vm = this;
return Object.assign({},
// 从父级添加所有监听器
this.$listeners,
{
//可以覆写或添加其他自定义监听器
// 这里确保组件配合`v-model`的工作
input (e) {
vm.$emit('input', e.target.value)
}
}
)
}
}
}
</script>
此时子组件就是一个完全透明的包裹容器,可以像使用普通input
一般使用它:1
<child v-model="value" label="name" @focus="focus_event">
再回到开头,如何向上传递数据更新呢,其实和$attrs
一样,在子组件模板上自定义事件监听,子组件将$listeners
向孙子组件传递,同时自己也可以触发其中的事件;1
2<!-- Fater.vue -->
<child @goodMoring="bar" @goodAfternoon="foo"></child>
1 | <!-- Child.vue --> |
1 | <!-- Grandson.vue --> |
参考资料
生命周期钩子Lifecycle Hook
vue
中的所有组件都是一个vue
实例,但根实例比其它实例多出了el
等选项,它们需要经过相同的生命周期,并调用每个生命周期阶段对应的钩子函数;
可以在父组件中指定子组件的生命周期函数:1
2<!-- Father.vue -->
<child @hook:created="hook_father"></child>
如果子组件中也指定了相应的生命周期函数,则会先执行子组件所定义的,再执行父组件传入的;
暂时想不出有什么实际用途,可能在动态创建子组件时可以统一在父组件中进行状态管理,而不是分散到各个子组件中;
混入(mixin)
混入 (mixins) 是一种分发 Vue 组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。
如上所述,可以将需要复用的props
, data
, methods
,生周期钩子函数等抽取出来成为一个单独的mixin
对象;当与子组件自定义的属性产生冲突时,会按照一定规则进行扩展或覆盖;
1 | <!-- mixin-example.vue --> |
1 | <!-- test.vue --> |