code_v-model、$attrs、$listenersjavascript
el: "#app-1", data() { return { text: '' } }, components: { 'component-1': { template: '<input type="text">' } }
<component-1 v-model="text"></component-1>
直接在表单组件的标签上使用v-model
,没有任何做用。vue
首先看看v-model
如何在原生input
元素上工做的。java
data() { return { text: '' } }
<input type="text" v-bind:value="text" v-on:input="text = $event.target.value">
上面的标签为v-model
指令的彻底形式,v-model
是下面代码的语法糖,经过事件处理函数与数据绑定完成双向绑定。须要注意的是事件处理函数的内容是默认的。git
v-bind:value="somedata" v-on:input="somedata = $event.target.value"
对比原生input
元素,能够得出,输入组件上的v-model
也是由事件处理与数据绑定组成。segmentfault
data() { return { text_1: '', text_2: '' } } //...... 'component-2-1': { template: '<input type="text" v-bind:value="value" v-on:input="eventHandler">', props: ['value'], methods: { eventHandler: function (event) { this.$emit('input', event.target.value) } } }
<component-2-1 v-model="text_1"></component-2-1> <component-2-1 v-bind:value="text_1" v-on:input="text_1 = arguments[0]"></component-2-1>
输入组件模板中,监听每次输入,每次输入时就触发input
自定义事件,并以元素的值做为该事件负载;在父组件上监听这个input
自定义事件,触发该事件时执行默认的方法,将事件负载传递给父组件本身的data
;这里要关注默认的处理方法somedata = arguments[0]
,这个arguments[0]
便是自定义事件的负载;若父组件数据修改了,会经过:value=props
下发给子组件,所以子组件须要定义一个Propsvalue
,子组件还要将value
绑定在本身的input
元素上,向其余使用该组件的地方传递变化。app
变化的过程: 输入 - 子组件触发自定义事件 - 父组件监听到事件 - 根据负载修改数据 - 将修改数据下发给组件 - 引发其余位置的变化
对于子组件模板上,绑定本身原生input
事件的方式,以上使用一个eventHandler
事件处理方法。咱们也能够在没有参数的v-on
上绑定一个事件对象(在computed
中定义),以下:函数
'component-2-2': { template: '<input type="text" v-bind:value="value" v-on="eventDict">', props: ['value'], computed: { eventDict: function () { return { input: event => this.$emit('input', event.target.value) } } } }
<component-2-2 v-model="text_2"></component-2-2> <component-2-2 v-bind:value="text_2" v-on:input="text_2 = arguments[0]"></component-2-2>
子组件props.value
用来传递变化给其余使用该输入组件的地方,若是没有它,只是单向的绑定。this
若是在多层组件结构中,只是单纯的数据通讯,那么使用$attrs、$listeners是最方便的。
在使用它们以前,先来看个选项inheritAttrs
。spa
data(){ return { foo:'foo', bar:'bar' } }, components:{ 'component-3':{ props:['foo'], template:'<span>{{foo}}</span>', } }
<dd><component-3 :foo="foo" :bar="bar"></component-3></dd>
在父组件中,咱们绑定了两个Props,foo
和bar
,下发数据给子组件时,子组件只定义了foo
,bar
没地方传,组件渲染时它就留在了子组件的根元素上。双向绑定
若是咱们不想在子组件根元素上保留这个属性,咱们能够设置选项inheritAttrs
为false。
inheritAttrs:false
使用$attrs
、$listeners
。
new Vue({ el: '#app-4', data() { return { firstData: 'firstData', secondData: 'secondData', thirdData: 'thirdData', fourthData: 'fourthData' } }, methods: { firstEvent: function () { console.log('第一层组件触发的事件') }, secondEvent: function () { console.log('第二层组件触发的事件') }, thirdEvent:function(){ console.log('第三层组件触发的事件') }, fourthEvent:function(){ console.log('第四层组件触发的事件') } }, components: { 'first': { 'props': ['firstData'], template: '<div><h1>{{firstData}}</h1><second v-bind="$attrs" v-on="$listeners"></second></div>', mounted() { this.$emit('first'); }, inheritAttrs:false, components:{ 'second':{ 'props':['secondData'], template:'<div><h2>{{secondData}}</h2><third v-bind="$attrs" @fourth="eventHandler" v-on="$listeners"></third></div>', mounted() { this.$emit('second'); }, methods: { eventHandler(payload){ $(this.$el).find('h2').after('<span>' + payload + '</span>') } }, inheritAttrs:false, components:{ 'third':{ 'props':['thirdData'], template:'<div><h3>{{thirdData}}</h3><fourth v-bind="$attrs" @fourth="eventHandler" v-on="$listeners"></fourth></div>', mounted() { this.$emit('third'); }, methods: { eventHandler(payload){ $(this.$el).find('h3').after('<span>' + payload + '</span>') } }, inheritAttrs:false, components:{ 'fourth':{ 'props':['fourthData'], template:'<div style="background: #9FEF4E;"><h4>{{fourthData}}</h4></div>', mounted() { this.$emit('fourth','负载在第四层上事件上的数据'); } } } } } } } } } })
<first :first-data="firstData" :second-data="secondData" :third-data="thirdData" :fourth-data="fourthData" @first="firstEvent" @second="secondEvent" @third="thirdEvent" @fourth="fourthEvent"></first>
$listeners
上保存着全部低层组件触发的自定义事件,使用v-on="$listeners"
将本层及如下层触发的事件传递给上一层,上一层中能够监听$listeners
对象中全部的事件。整个过程即一个事件广播的形态。
$attrs
上保存着全部未被下发的上层组件中的数据,各层组件使用v-bind="$attrs"
向下层组件传递下发的数据。$attrs
好像一块数据蛋糕,被高层组件拿走的部分,低层组件没法再使用。配合inheritAttrs:false
可使那些用不到的数据属性,不会保留在组件的根元素上。
v-on="$listeners"
向上广播事件,v-bind="$attrs"
向下下发数据
完整代码
示例结果是这样:
<div id="app-2"> <dl> <dt class="first-dt">使用$parent和$refs在父子组件间传递数据</dt> <dd> <label>父组件</label> <input type="text" v-model="message" @input="handle"> <span>{{message}}</span> </dd> <dd> <label>子组件</label> <component-21 ref="child"></component-21> <span>{{message}}</span> </dd> </dl> </div>
new Vue({ el:'#app-2', data:{ message:'' }, methods:{ handle(){ this.$refs.child.message = this.message } }, components:{ 'component-21':{ template:'<input type="text" v-model="message" @input="handle"/>', data(){ return {message:''} }, methods:{ handle(){ this.$parent.message = this.message } } } }, })
在子组件中可使用$parent
获取父组件数据,也能够修改它。
在父组件中可使用$refs
获取子组件数据,也能够修改,以前必须在视图节点上给子组件取一个名字如:ref="child"
(见组件引用 —— ref、$refs)。
就靠这两个属性,进行父子组件间的通讯。
经过该方法不只可在任意组件内进行通讯。建立一个全局的实例bus管理事件触发和监听
var bus = new Vue()
以后建立表单输入组件,在其输入事件中触发bus上的message
事件,在created
钩子中监听该事件。
Vue.component('component-a', { template:`<input type="text" v-model="message" @input="emitEvent"/>`, data(){ return {message:''} }, methods:{ emitEvent(){ bus.$emit('message',this.message) } }, created(){ var _this = this bus.$on('message', c_msg => { _this.message = c_msg }) } }) Vue.component('component-b', { template:`<input type="text" v-model="message" @input="emitEvent"/>`, data(){ return {message:''} }, methods:{ emitEvent(){ bus.$emit('message',this.message) } }, created(){ var _this = this bus.$on('message', c_msg => { _this.message = c_msg }) } }) new Vue({ data:{ message:'' }, methods:{ emitEvent(){ bus.$emit('message',this.message) } }, created(){ var _this = this bus.$on('message', c_msg => { _this.message = c_msg }) } }).$mount('#app-1')
<dl> <dt class="first-dt">使用全局View实例管理事件,在任意组件间传递数据</dt> <dd> <label>父组件</label> <input type="text" v-model="message" @input="emitEvent"> <span>{{message}}</span> </dd> <dd> <label>子组件a</label> <component-a></component-a> <span>{{message}}</span> </dd> <dd> <label>子组件b</label> <component-b></component-b> <span>{{message}}</span> </dd> </dl>
当一个组件输入时触发bus上的事件,全部组件都会监听到,并使用事件上的负载数据。