In-depth interpretation of the principle of provide/inject

provideUsed together with injectthe option requires, it allows the ancestor component to inject dependencies to all its descendants, and it will always take effect when its upstream and downstream relationships are established, no matter how deep the component hierarchy is.

1. Let's briefly review the use of provide/inject

as follows:

var Provider = {
    
    
    provide: {
    
    
        foo: "bar"
    }
}
var Child = {
    
    
    inject: ["foo"],
    created() {
    
    
        console.log(this.foo); // "bar"
    }
}

If it is used ES5 Symbolas a key, it is used as follows:

const s = Symbol();

var Provider = {
    
    
    provide() {
    
    
        return {
    
    
            [s]: "bar"
        }
    }
}
var Child = {
    
    
    inject: {
    
     s },
    created() {
    
    
        console.log(this.foo); // "bar"
    }
}

data/propsThe injected value can be accessed in :

var Provider = {
    
    
    provide: {
    
    
        foo: "bar",
        foo2: "bar2"
    }
}
var Child = {
    
    
    inject: ["foo"],
    props: {
    
    
        bar: {
    
    
            default() {
    
    
                return this.foo;
            }
        }
    },
    data() {
    
    
        return {
    
    
            bar2: this.foo2
        }
    }
}

injectDefault values ​​that can be set :

var Provider = {
    
    
    provide: {
    
    
        foo: "bar"
    }
}
var Child = {
    
    
    inject: {
    
    
        foo: {
    
     default: "foo"}
    }
}

If it needs to be injected from a property with a different name, use fromto indicate its source property:

var Provider = {
    
    
    provide: {
    
    
        foo: "bar"
    }
}
var Child = {
    
    
    inject: {
    
    
        foo: {
    
     
            from: "var",
            default: "foo"
        }
    }
}

2. The internal principle of inject

injectinitialized before data/props, and initialized provideafter data/props. The purpose of this is to make the injected content available to users data/propsin . injectThat is to say, in order to data/propsdepend inject, the initialization needs to injectbe placed in front of the initialization data/props.

First, we define the initialization injectfunction:

export function initInjections (vm: Component) {
    
    
  // 通过用户配置的inject,自底向上搜索可用的注入内容,并将搜索结果返回
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    
    
    toggleObserving(false) // 通知defineReactive不要将内容转换成响应式
    Object.keys(result).forEach(key => {
    
    
        defineReactive(vm, key, result[key])
    })
    toggleObserving(true)
  }
}

resolveInjectThe role of the function is configured by the user inject, searches the available injection content from bottom to top, and returns the search results. And toggleObserving(false)the role is to inform defineReactive not to convert the content into responsive.

So resolveInjecthow? injectThe main idea of ​​implementing this function is: read out the value set by the user in the current component key, and then loop keythrough each one keyfrom the current component to the parent component to find out whether there is a value, stop the loop if found, and finally convert all keycorresponding The value can be returned together.

export function resolveInject (inject: any, vm: Component): ?Object {
    
    
  if (inject) {
    
    
    const result = Object.create(null)
    /**
     * hasSymbol:是否支持Symbol
     */
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)

    return result
  }
}

The first step is to define an object resultto store our results, and then injectread keythem out. SymbolUse read if supported , Reflect.ownKeys(inject)read if not supported Object.keys(inject).

But we know injectthat the form of array is supported, if the form of array is used, such as:

var Provider = {
    
    
    provide: {
    
    
        foo: "bar"
    }
}
var Child = {
    
    
    inject: ["foo"],
    created() {
    
    
        console.log(this.foo); // "bar"
    }
}

Is it not possible to read injecteverything in it correctly key?

In fact, Vue.jsthe first step in instantiation is to normalize the data passed in by the user. If injectthe passed content is an array, the data will be normalized into an object and stored in fromthe property.

If the user is set injectlike this:

{
    
    
    inject: ["foo"]
}

Then after normalization it will look like this:

{
    
    
    inject: {
    
    
        foo: {
    
    
            from: "foo"
        }
    }
}

Next, you need to loop key. keyStarting from the current component, you will continue to check whether there is a value from the parent component. If you find it, stop the loop, and finally return all the keycorresponding values ​​​​together.

export function resolveInject (inject: any, vm: Component): ?Object {
    
    
  if (inject) {
    
    
    const result = Object.create(null)
    /**
     * hasSymbol:是否支持Symbol
     */
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
    
    
      const key = keys[i]
      /*
      * 通过from属性得到provide源属性
      * 当Vue.js被实例化时,会在上下文(this)中添加$options属性,这会把inject中提供的数据规格化,包括inject
      *    用户设置: { inject: [foo] }
      *   规格化:{ inject: { foo: { from: "foo" }}}
      */
      const provideKey = inject[key].from
      let source = vm // 一开始为当前实例
      // 自底向上寻找provide源属性
      while (source) {
    
    
        if (source._provided && hasOwn(source._provided, provideKey)) {
    
    
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent // 向上寻找
      }
    }
    return result
  }
}

In the above code, the outermost layer uses the for loop keys to get the keys in turn in the loop body, and then inject[key].fromget the provide source attribute provideKey. Then use a while loop through the source property to search for content. At the beginning source, it is the current component instance. If the corresponding value can be found in the original property, then set it to sourceand use break to exit the loop. Otherwise, set the source to the parent component instance for the next cycle._providedresult

If provideno injection is provided, the default configuration in inject will be used, the code is as follows:

export function resolveInject (inject: any, vm: Component): ?Object {
    
    
  if (inject) {
    
    
    const result = Object.create(null)
    /**
     * hasSymbol:是否支持Symbol
     */
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
    
    
      const key = keys[i]
      /*
      * 通过from属性得到provide源属性
      * 当Vue.js被实例化时,会在上下文(this)中添加$options属性,这会把inject中提供的数据规格化,包括inject
      *    用户设置: { inject: [foo] }
      *   规格化:{ inject: { foo: { from: "foo" }}}
      */
      const provideKey = inject[key].from
      let source = vm // 一开始为当前实例
      // 自底向上寻找provide源属性
      while (source) {
    
    
        if (source._provided && hasOwn(source._provided, provideKey)) {
    
    
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent // 向上寻找
      }
      // 没有source,设置默认值
      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
  }
}

sourceIf it is empty after the loop ends , it can be determined providethat the corresponding value injection has not been provided. At this time, it is necessary to read injectthe default value configured in . If so 'default' in inject[key], it proves that the default is configured, and if not, a warning will be printed in production. It is inject[key].defaultread provideDefault, but the default value supports functions and ordinary strings. At this time, it is necessary to judge provideDefaultwhether it is a function. If it is, execute it and store the result in result; if not, directly store it provideDefaultin result.

3. The complete code is as follows:

/**
 * 通过用户配置的inject,自底向上搜索可用的注入内容,并将搜索结果返回
 * 当使用provide注入内容时,其实是将内容注入到当前组件实例的_provide中,所以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)
    /**
     * hasSymbol:是否支持Symbol
     */
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
    
    
      const key = keys[i]
      // #6574 in case the inject object is observed...
      if (key === '__ob__') continue
      /*
      * 通过from属性得到provide源属性
      * 当Vue.js被实例化时,会在上下文(this)中添加$options属性,这会把inject中提供的数据规格化,包括inject
      *    用户设置: { inject: [foo] }
      *   规格化:{ inject: { foo: { from: "foo" }}}
      */
      const provideKey = inject[key].from
      let source = vm // 一开始为当前实例
      // 自底向上寻找provide源属性
      while (source) {
    
    
        if (source._provided && hasOwn(source._provided, provideKey)) {
    
    
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent // 向上寻找
      }
      // 没有source,设置默认值
      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
  }
}

Guess you like

Origin blog.csdn.net/weixin_50096821/article/details/123535108