組件(7) —— 通訊

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

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

clipboard.png

首先看看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"

clipboard.png

對比原生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>

clipboard.png

子組件props.value用來傳遞變化給其餘使用該輸入組件的地方,若是沒有它,只是單向的綁定。this

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

若是在多層組件結構中,只是單純的數據通訊,那麼使用$attrs、$listeners是最方便的。
在使用它們以前,先來看個選項inheritAttrsspa

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

相關文章
相關標籤/搜索