2024-10-04|閱讀時間 ‧ 約 0 分鐘

EP27 - 提供/注入

Provide / Inject 看字面上不知道想講什麼?
感覺似乎是重要的觀念!哈~
先看了再說吧~

屬性過度傳遞 - Prop Drilling​

通常,當我們需要將數據從父組件傳遞到子組件時,我們會使用 props。然而,想像一下當我們有一個大型組件樹,並且一個深層嵌套的組件需要從一個遙遠的祖先組件獲取某些東西的情況。僅使用 props 的話,我們將不得不在整個父級鏈中傳遞相同的 prop:

raw-image

注意,即使 <Footer> 組件可能根本不關心這些 props,它仍然需要聲明並傳遞它們,以便 <DeepChild> 可以訪問它們。如果父級鏈更長,沿途將有更多組件受到影響。這被稱為「props drilling」,處理起來絕對不好玩。

我們可以使用 provideinject 來解決 props drilling 的問題。祖先組件可以作為所有其後代的依賴提供者。後代樹中的任何組件,無論多深,都可以注入由其父級鏈中上層組件提供的依賴項。

提供 - Provide

要向組件的後裔組件提供資料,可以使用 provide() 函數:

<script setup>
import { provide } from 'vue'

provide(/* key */ 'message', /* value */ 'hello!')
</script>

如果不使用 <script setup>,請確保在 setup() 內同步調用 provide()

import { provide } from 'vue'

export default {
setup() {
provide(/* key */ 'message', /* value */ 'hello!')
}
}

provide() 函數接受兩個參數。第一個參數稱為注入鍵,可以是字串或符號。注入鍵由子組件使用,以查找要注入的所需值。一個組件可以多次調用 provide(),使用不同的注入鍵提供不同的值。

第二個參數是提供的值。該值可以是任何類型,包括響應式狀態,例如引用:

import { ref, provide } from 'vue'

const count = ref(0)
provide('key', count)

提供響應式值允許使用所提供值的後裔組件與提供者組件建立響應式連接。

應用層級的提供 - App-level Provide

除了在組件中提供資料,我們還可以在應用層級進行提供:

import { createApp } from 'vue'

const app = createApp({})

app.provide(/* key */ 'message', /* value */ 'hello!')

應用層級的提供對應用程式中渲染的所有組件都可用。這在編寫插件時特別有用,因為插件通常無法通過組件提供值。

注入 - Inject

要注入由祖先組件提供的資料,使用 inject() 函數:

<script setup>
import { inject } from 'vue'

const message = inject('message')
</script>

如果提供的值是 ref,它將按原樣注入,不會自動解包。這允許注入者組件保留與提供者組件的反應性連接。

Full provide + inject Example with Reactivity

同樣,如果不使用 <script setup>inject() 應該僅在 setup() 中同步調用:

import { inject } from 'vue'

export default {
setup() {
const message = inject('message')
return { message }
}
}

注入的預設值 - Injection Default Values

預設情況下,inject 假設注入的鍵在父層鏈中某處被提供。如果沒有提供該鍵,則會有運行時警告。

如果我們希望注入的屬性在沒有提供者的情況下也能工作,我們需要聲明一個預設值,類似於 props

// 如果沒有提供與 "message" 匹配的資料,`value` 將是 "default value"
const value = inject('message', 'default value')

在某些情況下,預設值可能需要通過調用函數或實例化新類來創建。為了避免不必要的計算或副作用,以防可選值未被使用,我們可以使用工廠函數來創建預設值:

const value = inject('key', () => new ExpensiveClass(), true)

第三個參數表示預設值應該被視為工廠函數。

處理反應性 - Working with Reactivity​

當使用反應性的提供 / 注入值時,建議盡可能將對反應性狀態的任何變更保持在提供者組件內部。這確保了提供的狀態及其可能的變更位於同一組件中,從而更容易在未來進行維護。

有時我們需要從注入者組件更新資料。在這種情況下,我們建議提供一個負責變更狀態的函數:

<!-- 在提供者組件內部 -->
<script setup>
import { provide, ref } from 'vue'

const location = ref('North Pole')

function updateLocation() {
location.value = 'South Pole'
}

provide('location', {
location,
updateLocation
})
</script>
<!-- 在注入者組件內部 -->
<script setup>
import { inject } from 'vue'

const { location, updateLocation } = inject('location')
</script>

<template>
<button @click="updateLocation">{{ location }}</button>
</template>

最後,如果您想確保通過 provide 傳遞的資料不能被注入者組件變更,可以使用 readonly() 將提供的值包裝起來:

<script setup>
import { ref, provide, readonly } from 'vue'

const count = ref(0)
provide('read-only-count', readonly(count))
</script>

使用 Symbol 作為鍵 - Working with Symbol Keys​

目前為止,我們在範例中使用的都是字串作為注入鍵。如果您在一個有許多依賴提供者的大型應用程式中工作,或者您正在編寫其他開發者會使用的組件,最好使用 Symbol 作為注入鍵以避免潛在的衝突。

建議在專用文件中導出這些 Symbols:

// keys.js
export const myInjectionKey = Symbol()
// 在提供者組件中
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'

provide(myInjectionKey, {
/* 要提供的數據 */
})
// 在注入者組件中
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'

const injected = inject(myInjectionKey)

參見:提供 / 注入類型

Q: 什麼是Symbol?

在 JavaScript 中,Symbol 是一種基本資料型別,用於創建唯一且不可變的值。Symbol 的主要作用是為物件屬性提供唯一的鍵,以避免屬性名的衝突。這對於大型應用程式和開發者間的協作尤其有用,因為它能確保不同的模組或組件不會無意間覆蓋彼此的屬性。

以下是一些關於 Symbol 的重要特性和使用方式:

  1. 唯一性:每個 Symbol 都是唯一的,即使兩個 Symbol 的描述相同,它們也不相等。
  2. 不可變性:一個 Symbol 一旦創建,它的值就不能改變。
  3. 用途:通常用作物件的鍵,確保不會與其他鍵發生衝突。

創建 Symbol

可以使用 Symbol() 函數創建 Symbol。可以選擇性地提供一個描述,用於調試:

const mySymbol = Symbol('mySymbol');

使用 Symbol 作為物件屬性鍵

可以將 Symbol 用作物件的屬性鍵:

const mySymbol = Symbol('mySymbol');
const obj = {
[mySymbol]: 'value'
};

console.log(obj[mySymbol]); // 輸出: 'value'

provide / inject 一起使用

在 Vue 中,可以使用 Symbol 作為 provideinject 的鍵,這樣可以避免鍵名衝突:

// keys.js
export const myInjectionKey = Symbol('myInjectionKey');

// 提供者組件中
import { provide } from 'vue';
import { myInjectionKey } from './keys.js';

provide(myInjectionKey, { data: 'value' });

// 注入者組件中
import { inject } from 'vue';
import { myInjectionKey } from './keys.js';

const injectedData = inject(myInjectionKey);
console.log(injectedData); // 輸出: { data: 'value' }

Symbol 的常見用途

  1. 私有屬性:通過 Symbol 鍵可以模擬私有屬性。
  2. 內建 Symbol:JavaScript 提供了許多內建的 Symbol,例如 Symbol.iterator,用於定義物件的默認迭代行為。

總結來說,Symbol 是一個強大的工具,用於確保物件屬性鍵的唯一性和不可變性,在避免名稱衝突和定義私有屬性方面非常有用。

這一章節的觀念真的蠻有趣!原來傳遞props也會有很多問題,
provide/inject就是拿來解決這些問題!
是不是創造一些新的解決方法之後,常常也會冒出新的問題?哈
不過總之最後能有效解決問題就好了www ~~
分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.