ホームページ  >  記事  >  ウェブフロントエンド  >  Vue3 での Provide と Inject の実装原則について話しましょう

Vue3 での Provide と Inject の実装原則について話しましょう

青灯夜游
青灯夜游転載
2022-02-16 20:09:592960ブラウズ

この記事では、Vue3 を深く理解し、Provide/Inject の実装原理を詳しく紹介しますので、皆様のお役に立てれば幸いです。

Vue3 での Provide と Inject の実装原則について話しましょう

Vue3 の Provide/Inject の実装原理は、実際にはプロトタイプとプロトタイプ チェーンを上手に利用することで実現されているため、Vue3 の Provide/Inject の実装原理を理解する前に、まずおさらいしてみましょう。プロトタイプとプロトタイプチェーンに関する知識。 [関連する推奨事項: vue.js ビデオ チュートリアル ]

プロトタイプとプロトタイプ チェーンの知識のレビュー

    #プロトタイプと
  • __proto__
  • #プロトタイプは一般に明示的プロトタイプと呼ばれ、
__proto__

は一般に暗黙的プロトタイプと呼ばれます。各関数が作成されると、デフォルトで、関数のプロトタイプ オブジェクトを表すプロトタイプという名前のプロパティが作成されます。

プロトタイプ チェーン
  • JS オブジェクトのプロパティにアクセスすると、JS はまずオブジェクトによって定義されたプロパティを検索します。見つからない場合は、これに従う オブジェクトの
__proto__

この暗黙のプロトタイプに関連付けられたチェーンは、前のオブジェクトを検索します。このチェーンはプロトタイプ チェーンと呼ばれます。 <pre class="brush:js;toolbar:false;">function Fn() {} Fn.prototype.name = &amp;#39;coboy&amp;#39; let fn1 = new Fn() fn1.age = 18 console.log(fn1.name) // coboy console.log(fn1.age) // 18</pre>fn1 は Fn 関数 new によって作成されたインスタンス オブジェクト、fn1.age はこのインスタンス オブジェクトの属性、fn1.name は Fn.prototype プロトタイプ オブジェクトから取得されます。これは、fn1 の

__proto__# であるためです。 ## 暗黙的なプロトタイプは、関数 Fn を指すプロトタイプ オブジェクト Fn.prototype です。プロトタイプ チェーンを使用すると、ある参照型が別の参照型のプロパティとメソッドを継承できます。

function Fn() {}
Fn.prototype.name = &#39;coboy&#39;
let fn1 = new Fn()
fn1.name = &#39;cobyte&#39;
console.log(fn1.name) // cobyte
インスタンス オブジェクト fn1 の属性名にアクセスすると、JS は最初にインスタンス オブジェクト fn1 の属性を検索します。たまたま fn1 が name 属性を定義しているため、独自の値コバイトを直接返します。それ以外の場合は、プロトタイプ チェーンに沿って Fn.prototype の検索が続けられ、その後 coboy に戻ります。

プロトタイプとプロトタイプ チェーンの知識を確認した後、Provide/Inject の実装原則を検討し始めました。

Provide の使用

setup()

provide を使用する場合、まず vue## から開始します。 #provide メソッドを明示的にインポートします。これにより、provide を呼び出して各プロパティを定義できるようになります。 provide この関数を使用すると、2 つのパラメータを通じてプロパティを定義できます

##name (

type)
  • value

  • import { provide } from &#39;vue&#39;
    
    export default {
      setup() {
        provide(&#39;name&#39;, &#39;coboy&#39;)
      }
    }
  • provide API 実装原則

provide API 実装原則とは何ですか?

provide 関数は次のように簡略化できます
export function provide(key, value) {
    // 获取当前组件实例
    const currentInstance: any = getCurrentInstance()
    if(currentInstance) {
        // 获取当前组件实例上provides属性
        let { provides } = currentInstance
        // 获取当前父级组件的provides属性
        const parentProvides = currentInstance.parent.provides
        // 如果当前的provides和父级的provides相同则说明还没赋值
        if(provides === parentProvides) {
            // Object.create() es6创建对象的另一种方式,可以理解为继承一个对象, 添加的属性是在原型下。
            provides = currentInstance.provides = Object.create(parentProvides)
        }
        provides[key] = value
    }
}

要約すると、provide API は、現在のコンポーネントのインスタンス オブジェクトを取得し、受信データを現在のコンポーネント インスタンス オブジェクトの Provides に格納します。そして、ES6 の新しい API Object.create を通じて、親コンポーネントの Provides 属性が、現在のコンポーネント インスタンス オブジェクトの Provides 属性のプロトタイプ オブジェクトに設定されます。

コンポーネント インスタンス オブジェクトの初期化時の Provides 属性の処理

ソース コードの場所: runtime-core/src/component.ts

インスタンス オブジェクトのソース コードを見ると、インスタンス コンポーネントのインスタンス オブジェクトに、parent と Provides という 2 つの属性があることがわかります。初期化中に、親コンポーネントが存在する場合は、親コンポーネントの Provides を現在のコンポーネント インスタンス オブジェクトの Provides に割り当てます。存在しない場合は、新しいオブジェクトを作成し、アプリケーション コンテキストの Provides 属性を新しいオブジェクトのプロトタイプ オブジェクトの属性に設定します。

Vue3 での Provide と Inject の実装原則について話しましょうInject の使用

setup()inject

を使用する場合は、

vue から開始する必要もあります 明示的なインポート。インポート後、これを呼び出して、公開されるコンポーネント モードを定義できます。 inject この関数には 2 つのパラメータがあります:

注入されるプロパティの名前

  • デフォルト値 (

    optional

    )
  • import { inject } from &#39;vue&#39;
    
    export default {
      setup() {
        const name = inject(&#39;name&#39;, &#39;cobyte&#39;)
        return {
          name
        }
      }
    }
    inject API 実装原則

では、この inject の実装原則は何ですか? API?

inject 関数は
export function inject(
  key,
  defaultValue,
  treatDefaultAsFactory = false
) {
  // 获取当前组件实例对象
  const instance = currentInstance || currentRenderingInstance
  if (instance) {
    // 如果intance位于根目录下,则返回到appContext的provides,否则就返回父组件的provides
    const provides =
      instance.parent == null
        ? instance.vnode.appContext && instance.vnode.appContext.provides
        : instance.parent.provides

    if (provides && key in provides) {
      return provides[key]
    } else if (arguments.length > 1) {
      // 如果存在1个参数以上
      return treatDefaultAsFactory && isFunction(defaultValue)
        // 如果默认内容是个函数的,就执行并且通过call方法把组件实例的代理对象绑定到该函数的this上
        ? defaultValue.call(instance.proxy) 
        : defaultValue
    }
  }
}

に簡略化できます。 inject ソース コード分析を通じて、inject が最初に現在のコンポーネントのインスタンス オブジェクトを取得し、次にそれがルート コンポーネントであるかどうかを判断することがわかります。ルート コンポーネントの場合は appContext Provides に戻り、それ以外の場合は親コンポーネントの Provides が返されます。

現在取得したキーに値がある場合は、その値を返します。ない場合は、デフォルトのコンテンツがあるかどうかを確認します。デフォルトのコンテンツが関数である場合は、それを実行し、call メソッドを使用してコンポーネント インスタンスのプロキシ オブジェクト。この関数にバインドしない場合は、デフォルトのコンテンツが直接返されます。

provide/inject实现原理总结

通过上面的分析,可以得知provide/inject实现原理还是比较简单的,就是巧妙地利用了原型和原型链进行数据的继承和获取。provide API调用设置的时候,设置父级的provides为当前provides对象原型对象上的属性,在inject获取provides对象中的属性值时,优先获取provides对象自身的属性,如果自身查找不到,则沿着原型链向上一个对象中去查找。

拓展:Object.create原理

方法说明

  • Object.create()方法创建一个新的对象,并以方法的第一个参数作为新对象的__proto__属性的值(以第一个参数作为新对象的构造函数的原型对象)
  • Object.create()方法还有第二个可选参数,是一个对象,对象的每个属性都会作为新对象的自身属性,对象的属性值以descriptor(Object.getOwnPropertyDescriptor(obj, 'key'))的形式出现,且enumerable默认为false

源码模拟

Object.myCreate = function (proto, propertyObject = undefined) {
    if (propertyObject === null) {
        // 这里没有判断propertyObject是否是原始包装对象
        throw &#39;TypeError&#39;
    } else {
        function Fn () {}
        // 设置原型对象属性
        Fn.prototype = proto
        const obj = new Fn()
        if (propertyObject !== undefined) {
            Object.defineProperties(obj, propertyObject)
        }
        if (proto === null) {
            // 创建一个没有原型对象的对象,Object.create(null)
            obj.__proto__ = null
        }
        return obj
    }
}

定义一个空的构造函数,然后指定构造函数的原型对象,通过new运算符创建一个空对象,如果发现传递了第二个参数,通过Object.defineProperties为创建的对象设置key、value,最后返回创建的对象即可。

示例

// 第二个参数为null时,抛出TypeError
// const throwErr = Object.myCreate({name: &#39;coboy&#39;}, null)  // Uncaught TypeError
// 构建一个以
const obj1 = Object.myCreate({name: &#39;coboy&#39;})
console.log(obj1)  // {}, obj1的构造函数的原型对象是{name: &#39;coboy&#39;}
const obj2 = Object.myCreate({name: &#39;coboy&#39;}, {
    age: {
        value: 18,
        enumerable: true
    }
})
console.log(obj2)  // {age: 18}, obj2的构造函数的原型对象是{name: &#39;coboy&#39;}

拓展:两个连续赋值的表达式

provides = currentInstance.provides = Object.create(parentProvides) 发生了什么?

Object.create(parentProvides) 创建了一个新的对象引用,如果只是把 currentInstance.provides 更新为新的对象引用,那么provides的引用还是旧的引用,所以需要同时把provides的引用也更新为新的对象引用。

来自《JavaScript权威指南》的解析

  • JavaScript总是严格按照从左至右的顺序来计算表达式
  • 一切都是表达式,一切都是运算
provides = currentInstance.provides = Object.create(parentProvides)

上述的provides是一个表达式,它被严格地称为“赋值表达式的左手端(Ihs)操作数”。 而右侧 currentInstance.provides = Object.create(parentProvides) 这一个整体也当做一个表达式,这一个整体赋值表达式的计算结果是赋值给了最左侧的providescurrentInstance.provides = Object.create(parentProvides) 这个表达式同时也是一个赋值表达式,Object.create(parentProvides)创建了一个新的引用赋值给了currentInstance这个引用上的provides属性

currentInstance.provides这个表达式的语义是:

  • 计算单值表达式currentInstance,得到currentInstance的引用
  • 将右侧的名字provides理解为一个标识符,并作为“.”运算的右操作数
  • 计算currentInstance.provides表达式的结果(Result)

currentInstance.provides当它作为赋值表达式的左操作数时,它是一个被赋值的引用,而当它作为右操作数时,则计算它的值。

注意:赋值表达式左侧的操作数可以是另一个表达式,而在声明语句中的等号左边,绝不可能是一个表达式。 例如上面的如果写成了let provides = xxx,那么这个时候,provides只是一个表达名字的、静态语法分析期作为标识符来理解的字面文本,而不是一个表达式

(学习视频分享:web前端教程

以上がVue3 での Provide と Inject の実装原則について話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。