[Vue 源码] Vue 3.2 - provide | inject 原理

109 阅读1分钟

代码运行结果

provide.gif

代码示例

    <div id="app"></div>
    <script>
      let {
        createApp,
        reactive,
        Fragment,
        toRefs,
        h,
        getCurrentInstance,
        provide,
        inject
      } = Vue

      const MyCpn = {
        setup(props, { emit, slots }) {
          const state = inject('state')
          
          return () => h('div', null, [state.count])
        }
      }

      const App = {
        setup() {
          const state = reactive({ count: 0 })
          provide('state', state)
          
          setTimeout(() => {
            state.count++
          }, 2000)
        },

        render() {
          return h(MyCpn, null)
        }
      }

      createApp(App).mount('#app')
    </script>

第一:mountComponent 挂载组件阶段, 创建每个组件实例的时候,每个子组件实例的 provides 属性,默认继承自父组件实例的 provides 属性。

  const instance: ComponentInternalInstance = {
    provides: parent ? parent.provides : Object.create(appContext.provides),
  }

第二:调用 Provide 函数:

  • 先获取到当前组件实例的 provides, 再获取到当前父亲组件实例的 provides。
  • 第一次两个 provides 相同,所以会通过 Object.create() 再 parentProvides 的基础上创建子 provides (子对象的原型链指向父对象)
  • 如果在子组件中多次 provide, 就会走到 else 逻辑,在子 provide 上进行赋值操作。
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
    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 通过 Object.create 关联起来,再生成自己的 provides。

第二:调用 inject 函数

  • instance.parent.provides 获取到 父组件的 Provides
  • return provides[key as string] 从 provides 中找到 key 属性对应的属性值,并返回。
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.`)
  }
}

子组件消费的是父组件的 provides ,不包括本组件的 provides。