理解 v-model、v-bind、$attrs、 $listener 和 .sync 及其在跨组件数据双向绑定上的应用
目标和内容: 在完全理解 v-model,$attrs, $listener 的基础上,实现父(A)、子(B)、后代(C)的三层组件结构中,A组件和C组件跨越B组件之间的数据双向绑定。一、v-model 的本质<MyList v-model="lovingVue"></MyList><!-- 其实是以下的语法糖 --><MyList ...
一、v-model 的本质
<MyList v-model="lovingVue"></MyList>
<!-- 其实是以下的语法糖 -->
<MyList :value="lovingVue" @input="(data) => lovingVue = data"></MyList>
没耐心理解代码的话,直接看下面的文字:
v-model 的本质是:父组件给子组件传一个名为 value 的prop,然后对子组件挂载一个名为 input 的事件监听。
当子组件手动 emit 这个input 事件时,携带的载荷自动赋值到v-model后绑的父组件变量上。
(所以其实不是自动双向绑定,还是需要手动emit input事件的。)
二、v-bind的本质
本质是 批量传入props
<Component v-bind="{a: foo, b: bar, c: baz}" />
<!-- 相当于 -->
<Component
:a=foo
:b=bar
:c=baz
/>
三、$attrs的本质
父组件以形如:foo="xxx"或者v-bind="{age:12}"传给子组件的属性,但凡没有被子组件的props接收的,都会被扔到子组件的$attrs里去。
(另外,被props指名接收的,都放入子组件的$props里。)
四、 $listeners的本质
父组件以 @eventName="fn" 或者 v-on:eventName="fn" 对子组件挂载事件监听。对子组件而言,父组件监听的事件都放在$listeners里。
如果子组件对后代组件使用v-on="$listeners",相当于对后代组件批量挂载了父组件对自己的事件监听。因此后代组件的emit会触发父组件的事件方法。
------------------------------------------------------------------------------------
(以下关于 vue 事件内部实现,前后文无关,可跳过。如有错误欢迎指正)
打印 $listeners,可以看到如下结构。是一个 事件名+回调函数(数组) 构成的对象。
图中是后代组件的$listeners。发现当在子组件中,如果对后代组件 v-on="$listeners" 的同时,新增同名监听如@on-page-change='xxx',那么父组件的回调、子组件的回调,都会被加进后代组件的回调函数栈中。
这说明,vue on-emit 事件的本质是 父组件 向子组件传递事件对象,对象由事件名和回调函数构成。子组件会根据传入,维护自己的 $listener 对象,对各事件创建或 push 回调函数栈。
当子组件执行$emit,是在依次弹出栈内回调函数,并执行。是交由子组件管理的范畴,父组件无感知。是典型的观察者模式。
------------------------------------------------------------------------------------
五、.sync的本质
<Component
:foo="val"
@update:foo="(payload)=>{ val = payload }"
/>
<!-- 实质是 语法糖 -->
<Component :foo.sync="val" />
// 子组件使用
this.$emit('update:foo', payload)
实质是更方便地实现数据双向绑定。即数据流向父->子天然实现,子->父只需子组件emit相关事件即可,从而实现双向绑定。
那么掌握了以上知识了之后,我们如何实现跨越三层的一个双向绑定呢?
<!-- A Component -->
<template>
<BComponent v-model="modalShow"></BComponent>
</template>
<script>
this.modalShow = true // 往下传递状态,直接到C
</script>
<!-- B Component -->
<template>
<CComponent v-bind="$attrs" v-on="$listeners"></CComponent>
</template>
<script>
// 中间组件也可以中途向两边更改状态,一致性需要手动保持
// this.$attrs.value = true
// this.$emit('input', true)
</script>
<!-- C Component -->
<script>
@Props()
value:boolean
handleClose(){
this.$emit('input', false) // 往上emit状态,直接到A
}
</script>
总结一下,主要是中间传递组件的 v-bind="$attrs" v-on="$listeners"。
父组件更改了数据,会因为 $atrrs 的传递自然地传递到后代组件。而后代组件 emit 一个 input 事件,会因为 $listeners 自然地冒到父组件处。又因为父组件的 v-model 而自动把新数据赋值到父组件变量上,因此实现了所谓的"双向绑定"。
更多推荐
所有评论(0)