组件通信方式以及部分实现原理

245 阅读1分钟

简介:Vue组件通信方式一直都是单向数据流,就是父组件数据的更改会影响子组件信息的更改,而子组件信息更改不会影响父组件信息更改,那么父子组件之间如何通信呢?

1.props和emit

父组件 假设msg="爸爸的爱"

   <son :dataMsg="msg"  @updateMsg="getMsgByChild"></son>

子组件

    <div class="content" >
          {{dataMsg}}
          <button @click="returnMsg">反馈</button>
      </div>
     props:{
          dataMsg:{
              type:String
          }
      },

      returnMsg(){
          this.$emit('updateMsg','反馈爱'); // 注意updateMsg方法会放在_event属性上,稍后讲中央事件统一解释
      }

父传子,通过dataMsg映射,子组件向父组件通过$emit传递信息,父组件通过@updateMsg接受

2.通过provide和inject监听(可祖与孙相通)

父组件

<son></son>
data () {
    return {
      name: '张三'
    }
  },
  provide () {
    return {
      name2: this.name
    }
  },

子组件

{{name2}} // 显示为张三
export default {
  inject: ['name2']
}

这个时候传递的信息不是响应式的,可以测试在mounted中将name改为李四。

mounted() {
    this.name = '李四'
}
可以看见显示依旧为张三。那么为什么provider不是响应式呢?可以看看provider的源码
const provideOption = vm.$options.provide
if (provideOption) {
    const provided = isFunction(provideOption) // 如果写法为provider:{}就算一个对象,如果为provider(){}就是一个函数。这里主要是为了转换成对象
      ? provideOption.call(vm)
      : provideOption
    if (!isObject(provided)) {
      return
    }
    const source = resolveProvided(vm) // 在vue上创建一个provider属性
    const keys = hasSymbol ? Reflect.ownKeys(provided) : Object.keys(provided) // 取出provider的属性
    //将属性挂载在_provider属性上
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      Object.defineProperty(
        source,
        key,
        Object.getOwnPropertyDescriptor(provided, key)!
      )
    }
}
所以initProvider仅仅只是在vue上创建一个__provided属性,并将其provider的属性和值挂载在上面。
那么inject实现了什么呢?主要是为了判定是否在provided里面,如果在里面就取出来。
const provideKey = inject[key].from
if (provideKey in vm._provided) {
    result[key] = vm._provided[provideKey]
}
所以我们应该怎么实现响应式呢?可以传递一个函数,去获取结果
// 父组件
provide () {
    return {
      name2: () => this.name
    }
}
// 子组件就去执行这个函数
{{name2()}}
也可以传递一个对象,这样挂载的就会是一个引用地址~~

3.通过.sync实现双向绑定

父组件

<son :showSon.sync="isShowSon" v-if="isShowSon"></son>

子组件

<div class="content" >
          我开始是显示的
          <button @click="returnMsg">{{showSon ? '隐藏' : '显示'}}</button>
      </div>
    
     returnMsg(){
          this.$emit('update:showSon',!this.showSon);
      }

注意父组件的showSon和子组件的prop['showSon']和update:showSon的对应。

4.通过refs或者refs或者child

5.通过eventbus

eventbus原理上是跟方法一$emit一样的,毕竟我们实现也是new Vue()。但不同的是我们通常会把它挂载
在原型的属性上,所以我们要找其触发的方法一般要去原型上找。下面我们看看vue的event实现的原理:
Vue.prototype.$on = function (
    event: string | Array<string>,
    fn: Function
    ): Component {
    const vm: Component = this
    if (isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      ;(vm._events[event] || (vm._events[event] = [])).push(fn)
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
}
Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      for (let i = 0, l = cbs.length; i < l; i++) {
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
}
分析起来其实比较简单,我们$on的时候就是将事件一个个塞进_event对象中,$emit就是将该事件取出,然后执行。值得一提的是$on可以监听多个事件。因为其接受数组。