最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • vue源码provide/inject原理解析

    正文概述 掘金(harry_yang)   2020-12-12   829

    依赖注入

    当深层嵌套的子孙组件想要拿到父组件的数据时,我们可以使用provide和inject。官网原理图: vue源码provide/inject原理解析

    使用案例: 我们假设组件嵌套结构如下:

    Root
    └─ TodoList
       ├─ TodoItem
       └─ TodoListFooter
          ├─ ClearTodosButton
          └─ TodoListStatistics
    

    我们通过provide和inject将组件TodoList的属性直接传给组件TodoListStatistics:

    const app = Vue.createApp({})
    
    app.component('todo-list', {
      data() {
        return {
          todos: ['张三', '李四']
        }
      },
      provide: { // provide是一个对象
        name: '王五'
      },
      template: `
        <div>
          {{ todos.length }}
        </div>
      `
    })
    
    app.component('todo-list-statistics', {
      inject: ['name'],
      created() {
        console.log(`Injected 属性: ${this.name}`) // > Injected 属性: 王五
      }
    })
    

    上例中的provide定义为一个对象。如果需要在provide里使用data中的属性,需要把provide定义成一个方法,否则会报错。

    app.component('todo-list', {
      data() {
        return {
          todos: ['张三', '李四']
        }
      },
      provide() { // provide是一个function
        return {
          todoLength: this.todos.length
        }
      },
      template: `
        ...
      `
    })
    

    依赖注入的优缺点如下: 优点: * 祖先组件不需要知道哪些后代组件使用它提供的数据; * 后代组件不需要知道被注入的数据来自哪里; 缺点: * 组件间的耦合较为紧密,不易重构; * 提供的属性是非响应式的;解决方案见官方文档

    源码解读

    组件实例初始化的时候会调用Vue.prototype._init,通过下面源码,我们可以知道: inject、provide的初始化时间在生命周期钩子函数beforeCreate之后,created之前。 **initInjections(vm)**解析inject是在初始化data/props之前, **initProvide(vm)**解析provide是在初始化data/props之后。 这也符合数据初始化的一个处理逻辑。

    Vue.prototype._init源码:

    //初始化inject和provide
    // expose real self
    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')
    

    initInjections函数,功能是**获取组件注册的inject属性合集,然后遍历合集进行响应式监听。**源码如下:

    export function initInjections (vm: Component) {
      const result = resolveInject(vm.$options.inject, vm) // 1、根据注册的inject,通过$parent向上查找对应的provide
      if (result) {
        toggleObserving(false)
        Object.keys(result).forEach(key => {
          /* istanbul ignore else */
          if (process.env.NODE_ENV !== 'production') {
            defineReactive(vm, key, result[key], () => {
              warn(
                `Avoid mutating an injected value directly since the changes will be ` +
                `overwritten whenever the provided component re-renders. ` +
                `injection being mutated: "${key}"`,
                vm
              )
            })
          } else {
            defineReactive(vm, key, result[key]) // 2、进行响应式监听
          }
        })
        toggleObserving(true)
      }
    }
    

    resolveInject函数,功能是通过$parent一层层向上查找祖先节点的数据,直到找到对应于inject的provide数据。

    export function resolveInject (inject: any, vm: Component): ?Object {
      if (inject) {
        // inject is :any because flow is not smart enough to figure out cached
        const result = Object.create(null)
        const keys = hasSymbol
          ? Reflect.ownKeys(inject)
          : Object.keys(inject)
    
        for (let i = 0; i < keys.length; i++) { // 遍历所有inject为其赋值
          const key = keys[i]
          // #6574 in case the inject object is observed...
          if (key === '__ob__') continue
          const provideKey = inject[key].from
          let source = vm
          while (source) { // 核心原理:通过$parent一层一层向上查找祖先节点的provide,找到则对inject进行赋值
            if (source._provided && hasOwn(source._provided, provideKey)) {
              result[key] = source._provided[provideKey]
              break
            }
            source = source.$parent
          }
          if (!source) {
            if ('default' in inject[key]) {
              const provideDefault = inject[key].default
              result[key] = typeof provideDefault === 'function'
                ? provideDefault.call(vm)
                : provideDefault
            } else if (process.env.NODE_ENV !== 'production') {
              warn(`Injection "${key}" not found`, vm)
            }
          }
        }
        return result
      }
    }
    

    initProvide函数,该方法单纯把组件注册的provide值,赋值给vm._provided,resolveInject中有使用到。

    export function initProvide (vm: Component) {
      const provide = vm.$options.provide
      if (provide) {
        vm._provided = typeof provide === 'function'
          ? provide.call(vm)
          : provide
      }
    }
    

    总结

    依赖注入,其核心原理就是通过$parent向上查找祖先组件中的provide,找到则赋值给对应的inject即可。 仔细一思量,老铁们会发想,依赖注入原理和JavaScript中的instanceof操作符原理有异曲同工之处。在instanceof中,通过__proto__向原型链中查找,如果__proto__与构造函数的prototype相等则返回true。哈哈哈哈哈,这就是研究原理的有趣之处。


    起源地下载网 » vue源码provide/inject原理解析

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元