biu biu biu coding

v-model $attrs $listeners

字数统计: 1.9k阅读时长: 8 min
2019/11/15 Share

v-model

本质上是一种语法糖 :bind=”value” @input=”method”

一些demo


表单控件上的使用

适用于 <input> <select> <textarea>

  • <input type=”text” > <textarea> text 和 textarea 元素使用 value 属性和 input 事件
  • <input type=”checkbox”> <input type=”radio”> 使用checked 属性和 change 事件
  • <select> value 作为 prop 并将 change 作为事件

自定义组件上的使用

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件

model配置项目: model配置项可以操作来自组件上v-model的默认值,prop可以修改绑定的参数,event可以更改

绑定的用于更新的方法名。


如下面的select,当input值改变时,会寻找change方法,在当前子组件作用域中其实是没有这个方法的,但是通过

model.event将默认的input方法改为了change方法,这个时候就能够调用此方法来进行更新。同样input此时默认获

取checkbox,通过修改model.prop来达到目的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 父组件
<base-checkbox v-model="lovingVue"></base-checkbox>

// 子组件
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
// 下面将v-model="checked" 拆成了语法糖
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})

$listeners

包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件——在创建更高层次的组件时非常有用。

用来实现跨组件双向绑定(以封装第三方组件为例)

不使用$listeners

实现双向绑定时,只能通过绑定计算属性,通过计算属性的set 来主动触发this.$emit

如下有一组例子:

第三方组件v-input,简单的输入框v-model。v-transfrom-input是我们对其

进行二次封装的组件,再供给页面中使用。这些逻辑都一块写在listeners.vue中。

第三方组件:v-input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Vue.component("v-input", {
props: {
value: String
},
// 如下是v-model=“value”语法糖的拆解
template: `
<input
:style="color"
type="text"
:bind="value"
v-on:input="$emit('input', $event.target.value)"
>
`
});

封装第三方组件:v-transform-input

想实现双向绑定值传递,需要绑定一个计算属性thisValue, 通过计算属性set来主动触发this.$emit(‘input’),实现上层父组件中v-model绑定值的更新。

主动触发是因为当前组件为非表单控件,并没有用户输入事件来主动触发更行上层input方法,所以要添加逻辑来主动进行触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Vue.component("v-transform-input", {
props: {
value: String
},
computed: {
thisValue: {
get() {
return this.value;
},
set(value) {
this.$emit("input", value); // 主动触发上层父子间更改数据
}
}
},
template: `
<v-input v-model="thisValue" @otherMethod="otherMethod">
</v-input>
`
});

listeners.vue使用封装好的组件

1
2
3
4
5
6
7
8
// template
<v-transform-inpu v-model="value"></v-transform-inpu>
// js
data() {
return {
value: ''
}
}

使用$listeners

利用listeners将父组件作用域绑定的方法绑定此组件作用域的特点。

因为$listeners将父组件上v-model绑定值的input方法都绑定到了v-transform中,所以第三方组件v-input

中触发this.$emit(‘input’)就能在v-transform中找到$listeners绑定来的input方法,这个input方法其实就

是最顶层v-model指令语法糖的默认input方法,更新的也是最顶层的那个value

封装第三方组件

1
2
3
4
5
6
Vue.component("v-transform-input", {
props: {
value: String
},
template: `<v-input :value="value" v-on="$listeners"></v-input>`
});

用来实现跨组件调用方法(以封装第三方组件为例)

在没有使用$listeners时

调用第三方组件提供的方法需要逐层添加事件绑定,来调用$emit

同样的组件:

第三方组件v-input,提供一个setColor方法改变按钮的字体颜色,供上层组件调用,

然后我们对其进行二次封装的v-transfrom-input,再供给页面中使用

第三方组件:v-input

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
Vue.component("v-input", {
props: {
value: String
},
data() {
return {
color: 'color: red'
}
},
methods: {
setColor() {
this.color = 'color: green';
this.$emit('otherMethod', 'green');
}
},
template: `
<div>
<input
:style="color"
type="text"
v-model="value"
>
<button @click="setColor">触发第三方组件方法</button>
</div>
`
});

封装第三方组件:v-transfrom-input

1
2
3
4
5
6
7
8
9
10
11
12
Vue.component("v-transform-input", {
methods: {
otherMethod(color) {
// 通过传统的$emit父子组件方法来调用
this.$emit('otherMethod', color);
}
}
template: `
<v-input @otherMethod="otherMethod">
</v-input>
`
});

使用封装好的组件: listeners.vue

1
2
3
4
5
6
7
8
// template
<v-transform-inpu @otherMethod="otherMethod"></v-transform-inpu>
// js
methods: {
otherMethod(color) {
console.log('第三方组件触发', color);
}
}

使用$listeners

同样利用listeners将父组件作用域绑定的方法绑定此组件作用域的特点。

上层父组件的otherMethod方法也合并到了当前组件v-transfrom-input的作用域中,v-inputsetColr中调

用this.$emit(‘otherMethod’), 也就能在v-transfrom-input上找到对应方法。

改写封装的第三方组件

1
2
3
Vue.component("v-transform-input", {
template: `<v-input v-on="$listeners"></v-input>`
});

当父组件双向绑定值没有初始化注册

还是那个例子,不做初始化双向绑定就会失效。可能在vue实例第一次渲染时候,没有遍历到这个值,就不会生成相应的obsever。
有待继续发掘。

使用封装好的组件: listeners.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
// template
<v-transform-input v-model="value" @otherMethod="otherMethod"></v-transform-input>
// js
data() {
return {
// value: '' // 这里不做初始化处理。
}
},
methods: {
otherMethod(color) {
console.log('第三方组件触发', color);
}
}

$attrs

包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过v-bind=”$attrs” 传入内部组件——在创建高级别的组件时非常有用。

使用

孙组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// template
<template>
<p :style="getColor">我是孙组件</p>
</template>
// script
export default{
// 如果在不在props中声明color 那么这个数据,请使用this.$arrts.color
// 如果声明,那么this.$props上就会有color
props: {
newColor: String
},
computed: {
getColor() {
return `color: ${this.newColor || "green"}`;
}
}
}

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
// template
<grand-son v-bind="$attrs"></grand-son>

// script
import GrandSon from './GrandSon';

export default {
...,
components: {
GrandSon
}
}
<>

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// template
// style class 不会在$attrs上
<son newColor="red" custom="febcat" style="font-size: 16px;" class="son"></son>

// script
import Son from './Son';

export default {
...,
components: {
Son
}
}

// css
.son{
font-weight: 500;
}

查看dom如下

$attrs


inheritAttrs禁用特性继承

  • 默认值为true
  • 如果你不希望组件的根元素继承特性,你可以在组件的选项中设置 inheritAttrs: false
  • 不会影响 style 和 class 的绑定。
1
2
3
4
Vue.component('my-component', {
inheritAttrs: false,
// ...
})

小结

v-model表单控件

  • 通常默认绑定value,数据更新方法为input
  • checkbox, radio 使用为checked,更新方法为change
  • select 默认绑定value, 数据更新为change

v-model非表单控件作为中间层组件(如封装第三方组件)

  • 未使用$listeners情况下需要通过v-model双向绑定计算属性,数据更新

    触发set方法,在set方法中进而主动触发this.$emit(‘input’)

  • 使用$listeners情况下,仅需配合$attrs就能达到目的,v-model也不必写

双向绑定值初始化

  • 不管任何形式的值,只要在data中没有初始化,那么双向绑定就不会成功

讨论

  • 为什么只有data初始化的值,才能真正做到双向绑定视图更新?




CATALOG
  1. 1. v-model
    1. 1.1. 表单控件上的使用
    2. 1.2. 自定义组件上的使用
    3. 1.3. $listeners
      1. 1.3.1. 用来实现跨组件双向绑定(以封装第三方组件为例)
      2. 1.3.2. 用来实现跨组件调用方法(以封装第三方组件为例)
      3. 1.3.3. 当父组件双向绑定值没有初始化注册
    4. 1.4. $attrs
      1. 1.4.1. 使用
      2. 1.4.2. inheritAttrs禁用特性继承
    5. 1.5. 小结
  2. 2. 讨论