Mmear's 😘.

vue学习笔记(一):一些概念

字数统计: 2.2k阅读时长: 9 min
2018/11/13 Share

又开新坑了,最近重看了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
2
3
4
5
6
7
// Fater.vue
<template>
<child-component
prop_defined="Prop will be inherited"
prop_undefined="Homeless">
</child-component>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Child-component.vue
<template>
<!-- 未定义的prop将会被绑定在根元素上,审查元素即可看见 -->
<div id="root">
<!-- 已定义的prop -->
{{ prop_defined }}
</div>
...
</template>
<script>
export default {
props: ['prop_defined'],
// inheritAttr: false;
mounted () {
// 未定义的prop
console.log(this.$attr) // { prop_undefined: 'Homeless' }
}
}
</script>

可以通过在子组件中设置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
2
3
4
5
6
7
8
9
10
11
<!-- Child.vue -->
<template>
<div id="恶人">
{{ label }}
<input
v-bind="$attrs"
:value="value"
@input="$emit('input', e.tartet.value)">
</input>
</div>
</template>

此时,父级的·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
2
3
4
5
<!-- Child.vue -->
<div>
<button @click="$listeners.goodAfternoon('Hi!')"></button>
<grandson v-on="$listeners"></grandson>
</div>
1
2
3
4
<!-- Grandson.vue -->
<div>
<button @click="$listeners.goodMoring('Hi!')"></button>
</div>

参考资料

Vue父子组件数据传递

Vue通信全揭秘


生命周期钩子Lifecycle Hook

vue中的所有组件都是一个vue实例,但根实例比其它实例多出了el等选项,它们需要经过相同的生命周期,并调用每个生命周期阶段对应的钩子函数;

可以在父组件中指定子组件的生命周期函数:

1
2
<!-- Father.vue -->
<child @hook:created="hook_father"></child>

如果子组件中也指定了相应的生命周期函数,则会先执行子组件所定义的,再执行父组件传入的;

暂时想不出有什么实际用途,可能在动态创建子组件时可以统一在父组件中进行状态管理,而不是分散到各个子组件中;


混入(mixin)

混入 (mixins) 是一种分发 Vue 组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。

如上所述,可以将需要复用的props, data, methods,生周期钩子函数等抽取出来成为一个单独的mixin对象;当与子组件自定义的属性产生冲突时,会按照一定规则进行扩展或覆盖;

1
2
3
4
5
6
7
8
9
10
11
12
<!-- mixin-example.vue -->
<script>
export default {
props: ['name', 'age', 'sex'],
methods: {
setName (name) {
this.name = name;
}
// ...
}
}
</script>
1
2
3
4
5
6
7
8
9
10
<!-- test.vue -->
<script>
import mixinItem from 'mixin-example'
export default {
mixins: [minxinItem],
methods: {
testMethod (name) { /* ... */ }
}
}
</script>
CATALOG
  1. 1. vm.attrs与vm.listeners实现组件通信
    1. 1.1. 被$attrs”收容”的非prop特性
      1. 1.1.1. 深层组件的Prop传递
      2. 1.1.2. 利用非Prop的绑定特性实现事件代理
    2. 1.2. $listeners与组件通信
    3. 1.3. 参考资料
  2. 2. 生命周期钩子Lifecycle Hook
  3. 3. 混入(mixin)