Provide/Inject の原則の詳細な解釈

provideRequired オプションとinject一緒に使用すると、祖先コンポーネントがそのすべての子孫に依存関係を注入できるようになり、コンポーネント階層がどれほど深くても、上流と下流の関係が確立されると常に有効になります。

1. Provide/inject の使用法を簡単に確認してみましょう

次のように:

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

ES5 Symbolキーとして使用する場合は、次のように使用されます。

const s = Symbol();

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

data/props注入された値には次の場所でアクセスできます。

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

設定できるinjectデフォルト値:

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

別の名前のプロパティから注入する必要がある場合は、fromソース プロパティを示すために使用します。

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

2. インジェクションの内部原理

injectの前に初期化されdata/propsprovideの後に初期化されましたdata/propsこの目的は、挿入されたコンテンツをのユーザーがdata/props利用できるようにすることです。injectつまり、data/propsdepend するためにはinject、初期化をinject初期化の前に置く必要がありますdata/props

まず、初期化inject関数を定義します。

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)
  }
}

resolveInject関数の役割はユーザーによって構成されinject、利用可能な注入コンテンツを下から上に検索し、検索結果を返します。そしてtoggleObserving(false)その役割は、コンテンツをレスポンシブに変換しないようにdefineReactiveに通知することです。

それで、resolveInjectどうやって?この関数を実装する主なアイデアは、現在のコンポーネントでユーザーがinject設定した値を読み取りkey、現在のコンポーネントから親コンポーネントまでkey各値をループしkeyて値があるかどうかを確認し、ループを停止することです。見つかった場合は、最後にkey対応するすべての値を変換して、一緒に返すことができます。

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
  }
}

最初のステップは、結果を保存するオブジェクトを定義しresult、結果をinject読み出すことですkeySymbolサポートされている場合は読み取りを使用しReflect.ownKeys(inject)サポートされていない場合は読み取りを使用しますObject.keys(inject)

ただし、inject次のような配列形式が使用されている場合、配列形式がサポートされることがわかっています。

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

inject中身をすべて正しく読み取ることはできないのでしょうかkey

実際、Vue.jsインスタンス化の最初のステップは、ユーザーによって渡されたデータを正規化することです。inject渡されたコンテンツが配列の場合、データはオブジェクトに正規化され、fromプロパティに格納されます。

ユーザーがinject次のように設定されている場合:

{
    
    
    inject: ["foo"]
}

正規化後は次のようになります。

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

次に、ループする必要がありますkeykey現在のコンポーネントから開始して、親コンポーネントからの値があるかどうかを確認し続けます。見つかった場合はループを停止し、最後に対応する値をすべてまとめて返しますkey

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
  }
}

上記のコードでは、最も外側の層は for ループ キーを使用してループ本体でキーを順番に取得し、inject[key].fromprovide source 属性を取得しますprovideKey次に、ソース プロパティを介して while ループを使用してコンテンツを検索します。最初はsource、現在のコンポーネント インスタンスです。対応する値が元のプロパティで見つかった場合は、それを に設定しsourcebreak を使用してループを終了します。それ以外の場合は、次のサイクルでソースを親コンポーネント インスタンスに設定します。_providedresult

インジェクションが提供されない場合provide、inject のデフォルト設定が使用されます。コードは次のとおりです。

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
  }
}

ループ終了後にsource空の場合は、provide対応する値の注入が行われていないと判断できるため、この際、injectで設定したデフォルト値を読み込む必要があります。そうであれば'default' in inject[key]、デフォルトが設定されていることを証明し、そうでない場合は、本番環境で警告が出力されます。inject[key].defaultreadとなっていますprovideDefaultが、デフォルト値は関数と通常の文字列をサポートしています このときprovideDefault関数かどうかを判断する必要があります 関数であれば実行して結果を result に格納します そうでない場合は直接 result に格納しprovideDefaultます。

3. 完全なコードは次のとおりです。

/**
 * 通过用户配置的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
  }
}

おすすめ

転載: blog.csdn.net/weixin_50096821/article/details/123535108