元件(7) —— 通訊

weixin_33766168發表於2018-04-16

在表單輸入元件節點上使用v-model

code_v-model、$attrs、$listeners

el: "#app-1",
data() {
    return {
        text: ''
    }
},
components: {
    'component-1': {
        template: '<input type="text">'
    }
}
<component-1 v-model="text"></component-1>

直接在表單元件的標籤上使用v-model,沒有任何作用。

clipboard.png

首先看看v-model如何在原生input元素上工作的。

data() {
    return {
        text: ''
    }
}
<input type="text" v-bind:value="text" v-on:input="text = $event.target.value">

上面的標籤為v-model指令的完全形式,v-model是下面程式碼的語法糖,通過事件處理函式與資料繫結完成雙向繫結。需要注意的是事件處理函式的內容是預設的。

v-bind:value="somedata"
v-on:input="somedata = $event.target.value"

clipboard.png

對比原生input元素,可以得出,輸入元件上的v-model也是由事件處理與資料繫結組成。

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元素上,向其他使用該元件的地方傳遞變化。

變化的過程: 輸入 - 子元件觸發自定義事件 - 父元件監聽到事件 - 根據負載修改資料 - 將修改資料下發給元件 - 引起其他位置的變化

對於子元件模板上,繫結自己原生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>

clipboard.png

子元件props.value用來傳遞變化給其他使用該輸入元件的地方,如果沒有它,只是單向的繫結。

使用$attrs、$listeners在多層次的元件結構中實現通訊

如果在多層元件結構中,只是單純的資料通訊,那麼使用$attrs、$listeners是最方便的。
在使用它們之前,先來看個選項inheritAttrs

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,foobar,下發資料給子元件時,子元件只定義了foobar沒地方傳,元件渲染時它就留在了子元件的根元素上。

圖片描述

如果我們不想在子元件根元素上保留這個屬性,我們可以設定選項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>

clipboard.png

$listeners上儲存著所有低層元件觸發的自定義事件,使用v-on="$listeners"將本層及以下層觸發的事件傳遞給上一層,上一層中可以監聽$listeners物件中所有的事件。整個過程即一個事件廣播的形態。

clipboard.png

$attrs上儲存著所有未被下發的上層元件中的資料,各層元件使用v-bind="$attrs"向下層元件傳遞下發的資料。$attrs好像一塊資料蛋糕,被高層元件拿走的部分,低層元件無法再使用。配合inheritAttrs:false可以使那些用不到的資料屬性,不會保留在元件的根元素上。

v-on="$listeners"向上廣播事件,v-bind="$attrs"向下下發資料

父子元件的引用

完整程式碼
示例結果是這樣:

clipboard.png

<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

通過該方法不僅可在任意元件內進行通訊。建立一個全域性的例項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上的事件,所有元件都會監聽到,並使用事件上的負載資料。

clipboard.png

相關文章