vue3源码学习9-依赖注入

vue3.0中我们使用依赖注入的API函数provide和inject,可以在setup函数中调用它们,子孙组件中访问祖先组件提供的数据,祖先组件不需要知道哪些后代组件在使用它的数据,后代组件也不需要住到注入的数据来自哪里,先看provide API的实现packages/runtime-core/src/apiInject.ts:

export function provide<T>(key: InjectionKey<T> | string | number, value: T) {
  if (!currentInstance) {
    if (__DEV__) {
      warn(`provide() can only be used inside setup().`)
    }
  } else {
    let provides = currentInstance.provides
    // by default an instance inherits its parent's provides object
    // but when it needs to provide values of its own, it creates its
    // own provides object using parent provides object as prototype.
    // this way in `inject` we can simply look up injections from direct
    // parent and let the prototype chain do the work.
    const parentProvides =
      currentInstance.parent && currentInstance.parent.provides
    if (parentProvides === provides) {
      provides = currentInstance.provides = Object.create(parentProvides)
    }
    // TS doesn't allow symbol as index type
    provides[key as string] = value
  }
}

之前创建组件实例时,组件实例的provides对象指向父组件实例的provides对象packages/runtime-core/src/component.ts:

export function createComponentInstance(
  vnode: VNode,
  parent: ComponentInternalInstance | null,
  suspense: SuspenseBoundary | null
) {
	const instance: ComponentInternalInstance = {
		// 依赖注入相关
		// 组件实例的provides继承它的父组件,当实例组件需要提供自己的值时,使用父级提供的对象创建自己的provides原型,
		// 这样在inject时,就可以通过原型链查找直接父级提供的数据。当前子孙组件声明的相同key的数据会覆盖父级provides的数据
		provides: parent ? parent.provides : Object.create(appContext.provides),
		// 其它属性。。。
	}
}

接下来看inject API的实现packages/runtime-core/src/apiInject.ts:

export function inject(
  key: InjectionKey<any> | string,
  defaultValue?: unknown,
  treatDefaultAsFactory = false
) {
  // fallback to `currentRenderingInstance` so that this can be called in
  // a functional component
  const instance = currentInstance || currentRenderingInstance
  if (instance) {
    // #2400
    // to support `app.use` plugins,
    // fallback to appContext's `provides` if the instance is at root
    const provides =
      instance.parent == null
        ? instance.vnode.appContext && instance.vnode.appContext.provides
        : instance.parent.provides

    if (provides && (key as string | symbol) in provides) {
      // TS doesn't allow symbol as index type
      return provides[key as string]
    } else if (arguments.length > 1) {
      return treatDefaultAsFactory && isFunction(defaultValue)
        ? defaultValue.call(instance.proxy)
        : defaultValue
    } else if (__DEV__) {
      warn(`injection "${String(key)}" not found.`)
    }
  } else if (__DEV__) {
    warn(`inject() can only be used inside setup() or functional components.`)
  }
}

inject第一个参数是provides提供数据的key,层层查找父级提供的数据,第二个参数是默认值,如果找不到数据,则直接返回默认值。如果既找不到数据,有没有默认值,在非生产环境下报警告。

依赖注入和模块化都可以共享数据,但是又有以下几点不同:

1.作用域不同:依赖注入是局部的,只针对于提供数据的这个组件的所有后代组件,but模块化的作用域是全局范围的,可以在任何地方引用它导出的数据。

2.数据来源不同:依赖注入不需要知道注入的数据来自哪里,but模块化提供的数据,必须明确来自哪个模块。

3.上下文不同:依赖注入可以根据不同的组件上下文提供不同的数据给后代组件,but模块化提供的数据没有任何上下文。仅是这个模块定义的数据。

posted @ 2022-09-15 10:39  菜菜123521  阅读(400)  评论(0编辑  收藏  举报