Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vue源码provide/inject原理解析 #2

Open
webharry opened this issue Dec 11, 2020 · 0 comments
Open

vue源码provide/inject原理解析 #2

webharry opened this issue Dec 11, 2020 · 0 comments

Comments

@webharry
Copy link
Owner

webharry commented Dec 11, 2020

vue源码provide/inject原理解析

依赖注入

当深层嵌套的子孙组件想要拿到父组件的数据时,我们可以使用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。哈哈哈哈哈,这就是研究原理的有趣之处。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant