在表單輸入元件節點上使用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
,沒有任何作用。
首先看看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"
對比原生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>
子元件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,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
通過該方法不僅可在任意元件內進行通訊。建立一個全域性的例項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上的事件,所有元件都會監聽到,並使用事件上的負載資料。