自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面試官:來談談Vue3的Provide和Inject實現多級傳遞的原理

開發(fā) 前端
在孫子組件中,他的parent就是子組件。前面我們講過了如果沒有在組件內使用provide?注入東西(很明顯這里的子組件確實沒有注入任何東西),那么就會直接使用他的父組件的provides?屬性對象,所以這里的子組件是直接使用的是父組件中的provides?屬性對象。

前言

沒有看過provide和inject函數源碼的小伙伴可能覺得他們實現數據多級傳遞非常神秘,其實他的源碼非常簡單,這篇文章歐陽來講講provide和inject函數是如何實現數據多級傳遞的。ps:本文中使用的Vue版本為3.5.13。

看個demo

先來看個demo,這個是父組件,代碼如下:

<template>
  <ChildDemo />
</template>

<script setup>
import ChildDemo from "./child.vue";
import { ref, provide } from "vue";
// 提供響應式的值
const count = ref(0);
provide("count", count);
</script>

在父組件中使用provide為后代組件注入一個count響應式變量。

再來看看子組件child.vue代碼如下:

<template>
  <GrandChild />
</template>
<script setup>
import GrandChild from "./grand-child.vue";
</script>

從上面的代碼可以看到在子組件中什么事情都沒做,只渲染了孫子組件。

我們再來看看孫子組件grand-child.vue,代碼如下:

<script setup>
import { inject } from "vue";

// 注入響應式的值
const count = inject("count");
console.log("inject count is:", count);
</script>

從上面的代碼可以看到在孫子組件中使用inject函數拿到了父組件中注入的count響應式變量。

provide函數

我們先來debug看看provide函數的代碼,給父組件中的provide函數打個斷點,如下圖:

圖片圖片

刷新頁面,此時代碼將會停留在斷點處。讓斷點走進provide函數,代碼如下:

function provide(key, value) {
  if (!currentInstance) {
    if (!!(process.env.NODE_ENV !== "production")) {
      warn$1(`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);
    }
    provides[key] = value;
  }
}

首先判斷currentInstance是否有值,如果沒有就說明當前沒有vue實例,也就是說當前調用provide函數的地方是不在setup函數中執(zhí)行的,然后給出警告provide只能在setup中使用。

然后走進else邏輯中,首先從當前vue實例中取出存的provides屬性對象。并且通過currentInstance.parent.provides拿到父組件vue實例中的provides屬性對象。

這里為什么需要判斷if (parentProvides === provides)呢?

因為在創(chuàng)建子組件時會默認使用父組件的provides屬性對象作為父組件的provides屬性對象。代碼如下:

const instance: ComponentInternalInstance = {
  uid: uid++,
  vnode,
  type,
  parent,
  provides: parent ? parent.provides : Object.create(appContext.provides),
  // ...省略
}

從上面的代碼可以看到如果有父組件,那么創(chuàng)建子組件實例的時候就直接使用父組件的provides屬性對象。

所以這里在provide函數中需要判斷if (parentProvides === provides),如果相等說明當前父組件和子組件是共用的同一個provides屬性對象。此時如果子組件調用了provide函數,說明子組件需要創(chuàng)建自己的provides屬性對象。

并且新的屬性對象還需要能夠訪問到父組件中注入的內容,所以這里以父組件的provides屬性對象為原型去創(chuàng)建一個新的子組件的,這樣在子組件中不僅能夠訪問到原型鏈中注入的provides屬性對象,也能夠訪問到自己注入進去的provides屬性對象。

最后就是執(zhí)行provides[key] = value將當前注入的內容存到provides屬性對象中。

inject函數

我們再來看看inject函數是如何隔了一層子組件從父組件中如何取出數據的,還是一樣的套路,給孫子組件中的inject函數打個斷點。如下圖:

圖片圖片

將斷點走進inject函數,代碼如下:

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

  // also support looking up from app-level provides w/ `app.runWithContext()`
  if (instance || currentApp) {
    const provides = currentApp
      ? currentApp._context.provides
      : instance
        ? instance.parent == null
          ? instance.vnode.appContext && instance.vnode.appContext.provides
          : instance.parent.provides
        : undefined

    if (provides && key in provides) {
      return provides[key]
    } else if (arguments.length > 1) {
      return treatDefaultAsFactory && isFunction(defaultValue)
        ? defaultValue.call(instance && 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.`)
  }
}

首先拿到當前渲染的vue實例賦值給本地變量instance。接著使用if (instance || currentApp)判斷當前是否有vue實例,如果沒有看看有沒有使用app.runWithContext手動注入了上下文,如果注入了那么currentApp就有值。

接著就是一串三元表達式,如果使用app.runWithContext手動注入了上下文,那么就優(yōu)先從注入的上下文中取出provides屬性對象。

如果沒有那么就看當前組件是否滿足instance.parent == null,也就是說當前組件是否是根節(jié)點。如果是根節(jié)點就取app中注入的provides屬性對象。

如果上面的都不滿足就去取父組件中注入的provides屬性對象,前面我們講過了在inject函數階段,如果子組件內沒有使用inject函數,那么就會直接使用父組件的provides屬性對象。如果子組件中使用了inject函數,那么就以父組件的provides屬性對象為原型去創(chuàng)建一個新的子組件的provides屬性對象,從而形成一條原型鏈。

所以這里的孫子節(jié)點的provides屬性對象中當然就能夠拿到父組件中注入的count響應式變量,那么if (provides && key in provides)就滿足條件,最后會走到return provides[key]中將父組件中注入的響應式變量count原封不動的返回。

還有就是如果我們inject一個沒有使用provide存入的key,并且傳入了第二個參數defaultValue,此時else if (arguments.length > 1)就滿足條件了。

在里面會去判斷是否傳入第三個參數treatDefaultAsFactory,如果這個參數的值為true,說明第二個參數defaultValue可能是一個工廠函數。那么就執(zhí)行defaultValue.call(instance && instance.proxy)將defaultValue的當中工廠函數的執(zhí)行結果進行返回。

如果第三個參數treatDefaultAsFactory的值不為true,那么就直接將第二個參數defaultValue當做默認值返回。

總結

這篇文章講了使用provide和inject函數是如何實現數據多級傳遞的。

在創(chuàng)建vue組件實例時,子組件的provides屬性對象會直接使用父組件的provides屬性對象。如果在子組件中使用了provide函數,那么會以父組件的provides屬性對象為原型創(chuàng)建一個新的provides屬性對象,并且將provide函數中注入的內容塞到新的provides屬性對象中,從而形成了原型鏈。

在孫子組件中,他的parent就是子組件。前面我們講過了如果沒有在組件內使用provide注入東西(很明顯這里的子組件確實沒有注入任何東西),那么就會直接使用他的父組件的provides屬性對象,所以這里的子組件是直接使用的是父組件中的provides屬性對象。所以在孫子組件中可以直接使用inject函數拿到父組件中注入的內容。

責任編輯:武曉燕 來源: 前端歐陽
相關推薦

2021-05-27 10:36:34

ProvideInjectVue3

2021-12-09 08:49:14

Vue 3 Provide Inject

2024-02-20 14:10:55

系統(tǒng)緩存冗余

2024-04-11 13:10:00

Vue3Reactive響應性

2022-06-29 16:59:21

Vue3Vue2面試

2024-06-13 08:01:19

2018-10-22 14:28:26

面試官數據公司

2024-03-14 14:56:22

反射Java數據庫連接

2024-07-31 08:28:37

DMAIOMMap

2024-12-06 07:00:00

2024-09-20 08:36:43

零拷貝數據傳輸DMA

2024-03-22 06:56:24

零拷貝技術數據傳輸數據拷貝

2024-08-22 10:39:50

@Async注解代理

2024-03-05 10:33:39

AOPSpring編程

2025-03-07 00:00:10

2020-12-09 10:29:53

SSH加密數據安全

2024-09-25 12:26:14

2024-06-04 09:02:03

2021-06-07 17:12:22

線程安全Atomic

2025-02-28 00:00:00

點贊
收藏

51CTO技術棧公眾號