一、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 而自动把新数据赋值到父组件变量上,因此实现了所谓的"双向绑定"。

Logo

前往低代码交流专区

更多推荐