EP27 - 提供/注入

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

屬性過度傳遞 - Prop Drilling​

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

raw-image

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

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

raw-image

提供 - 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 ~~
avatar-img
2會員
70內容數
分享生活趣事~
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
卡關的人生 的其他內容
通過使用插槽,組件可以變得更加靈活和可重用,例如 <FancyButton> 可以讓父組件提供按鈕的內部內容,而 <BaseLayout> 則可以使用命名插槽來指定不同區域的內容。插槽還可以設置預設內容,當父組件未提供插槽內容時,會渲染預設內容。
當組件渲染單根元素時,這些屬性會自動添加到根元素中。如果子組件的根元素已有class或style屬性,則會與從父組件繼承的屬性合併。v-on事件監聽器也遵循相同規則。當組件嵌套時,降級屬性會自動轉發給子組件。
從 3.4 開始,推薦使用 defineModel() 巨集來自動化這個過程,讓寫法更加簡潔,支持多個 v-model 綁定,並可直接使用自定義修飾符。新方法還能同步父子組件的值,減少同步問題,使組件的開發與維護變得更方便
子組件可以使用 $emit 方法來發送自定義事件,父組件則使用 v-on 來監聽這些事件。事件參數可以通過 $emit 傳遞,並在父組件中使用內聯箭頭函數或方法接收。使用 defineEmits 可以顯式宣告事件並進行驗證,提升代碼可讀性和可維護性。
props的值可以是字符串數組或物件語法,每個屬性的鍵是prop的名字,值是預期類型的建構函數。當解構props時,Vue會自動添加props.前綴以保持響應性。Prop驗證確保了數據類型和要求的一致性,使開發更嚴謹。
接下來要深入探討組件註冊的內容!Vue 組件需要註冊,讓 Vue 知道在模板中遇到組件時在哪裡找到實現。註冊方式有全局和局部兩種。全局註冊適用於通用、頻繁使用的組件,例如按鈕、模態框等;而局部註冊適用於特定頁面或功能,能明確依賴關係,提升維護性。回顧之前的範例,我們都是使用局部註冊呢!
通過使用插槽,組件可以變得更加靈活和可重用,例如 <FancyButton> 可以讓父組件提供按鈕的內部內容,而 <BaseLayout> 則可以使用命名插槽來指定不同區域的內容。插槽還可以設置預設內容,當父組件未提供插槽內容時,會渲染預設內容。
當組件渲染單根元素時,這些屬性會自動添加到根元素中。如果子組件的根元素已有class或style屬性,則會與從父組件繼承的屬性合併。v-on事件監聽器也遵循相同規則。當組件嵌套時,降級屬性會自動轉發給子組件。
從 3.4 開始,推薦使用 defineModel() 巨集來自動化這個過程,讓寫法更加簡潔,支持多個 v-model 綁定,並可直接使用自定義修飾符。新方法還能同步父子組件的值,減少同步問題,使組件的開發與維護變得更方便
子組件可以使用 $emit 方法來發送自定義事件,父組件則使用 v-on 來監聽這些事件。事件參數可以通過 $emit 傳遞,並在父組件中使用內聯箭頭函數或方法接收。使用 defineEmits 可以顯式宣告事件並進行驗證,提升代碼可讀性和可維護性。
props的值可以是字符串數組或物件語法,每個屬性的鍵是prop的名字,值是預期類型的建構函數。當解構props時,Vue會自動添加props.前綴以保持響應性。Prop驗證確保了數據類型和要求的一致性,使開發更嚴謹。
接下來要深入探討組件註冊的內容!Vue 組件需要註冊,讓 Vue 知道在模板中遇到組件時在哪裡找到實現。註冊方式有全局和局部兩種。全局註冊適用於通用、頻繁使用的組件,例如按鈕、模態框等;而局部註冊適用於特定頁面或功能,能明確依賴關係,提升維護性。回顧之前的範例,我們都是使用局部註冊呢!
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
「嵌入 Embedding」這個概念, 是指將「資料 Data」轉換為「向量 Vector」格式的過程。 資料可以是影片的樣本, 可以是音訊的樣本, 可以是圖片的樣本, 可以是文字的樣本。 不同類型的樣本, 可以透過相對應的預訓練神經網路模型, 將資料
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
「嵌入 Embedding」這個概念, 是指將「資料 Data」轉換為「向量 Vector」格式的過程。 資料可以是影片的樣本, 可以是音訊的樣本, 可以是圖片的樣本, 可以是文字的樣本。 不同類型的樣本, 可以透過相對應的預訓練神經網路模型, 將資料
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相