理解组件通讯之:provide / inject

理解组件通讯之:provide / inject

在前端的Vue项目中,若是须要跨组件通讯,好比父组件A,子组件B,孙子组件C,A给C传递数据,又不想使用Vuex,Bus等方式,引入第三方库,想更简单的实现组件跨级通讯,那么Vue的原生API,provide / inject 是个不错的选择。html

什么是 provide / inject

provide / inject 是vue 2.2.0 版本增长的方法,这对选项须要一块儿使用,以容许一个祖先组件向其全部子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。前端

文档地址:cn.vuejs.org/v2/api/#pro…vue

vue官方文档说,主要为高阶插件/组件库提供用例,并不推荐直接用于应用程序代码中。react

虽然官方这么建议,可是若是学会了这种方法,结合须要的业务,仍是能发挥出很好的效果的。vuex

使用方式

  • provide:Object | () => Objectapi

  • inject:Array<string> | { [key: string]: string | Symbol | Object }数组

  • provide :能够是一个对象或者是一个返回对象的函数bash

  • inject :能够是一个字符串类型的数组或者是一个对象app

// A.vue
export default {
  // provide 是一个对象
  provide: {
    name: 'dale'
  }
}

// B.vue
export default {
  // inject 为一个字符串数组
  inject: ['name'],
  mounted () {
    console.log(this.name);  // dale
  }
}

复制代码

可是须要注意的是provideinject 绑定并非可响应的,也就是说父组件改变了值,默认状况下后辈组件是不会响应式变化的,可是这个能够用其余方法实现响应式变化。ide

好比给 provide 里面传入响应式对象:

// A.vue
export default {
    data(){
      return {
        number:0
      }
    },
  // 
  // provide 是一个返回兑现的函数
  provide() {
    let observeObject = this.$data
    return {
      name: observeObject
    }
  },
  methods:{
    // 改变number ,B.vue 会接收到更新,由于$data是一个响应式对象
  }
  
}

复制代码

采用 Vue.observable 构建出一个响应式对象,一样能够实现响应式更新数据。

// A.vue
const observeObject = Vue.observable({ number: 0 })
export default {

  // provide 是一个返回兑现的函数
  provide() {
    return {
      name: observeObject
    }
  },
  methods:{
    // observeObject.number 变化,B.vue 会接收到更新,由于observeObject是一个响应式对象
    // data的返回对象也是通过此api处理的
  }
  
}

复制代码

响应式原理: cn.vuejs.org/v2/guide/re…

扩展延伸

provideinject 的其余用法,高级技巧尝试。

app.vue 文件中把this 做为provide的值,能够把实例对外提供。全部子组件均可以直接经过 this.app.xxx 来访问 app.vue 的 data、computed、methods 等内容。

app.vue 是整个项目第一个被渲染的组件,并且只会渲染一次(即便切换路由,app.vue 也不会被再次渲染),利用这个特性,比较适合作一次性全局的状态数据管理,好比全局的登陆信息等, 而不须要使用vuex等第三方状态管理工具。

app.vue

export default {
    provide () {
      return {
        app: this
      }
    }
    // ...
 }
复制代码
// 子组件
    <template>
      <div>
        {{ app.userInfo }}
      </div>
    </template>
    <script>
      export default {
        inject: ['app']
      }
    </script>

复制代码

源码分析

vue 2.x 源码

  • inject/provide 本质仍是经过$parent向上查找祖先节点数据
// src/core/instance/init.js
Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    ...
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

复制代码
// inject里的key,经过$parent向上找到provide值,再进行响应式监听
export function initInjections (vm: Component) {
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    Object.keys(result).forEach(key => {
        defineReactive(vm, key, result[key]) // 响应式数据
    })
  }
}

export function resolveInject (inject: any, vm: Component): ?Object {
  if (inject) {
    const result = Object.create(null)
    const keys = Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      const provideKey = inject[key].from
      let source = vm
      // 循环向上,直到拿到祖先节点中的provide值
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey] // provide是在initProvide中设置的
          break
        }
        source = source.$parent // 关键代码
      }
    }
    return result
  }
}

复制代码
// 单纯把provide值,赋值给vm._provided。initInject中会使用到
export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

复制代码
相关文章
相关标签/搜索