跳轉到內容

提供/注入

本頁面假設您已經閱讀了元件基礎。如果是初次接觸元件,請先閱讀。

屬性鑽取

通常,當我們需要從父元件傳遞資料到子元件時,我們會使用props。但是,想象一下我們有一個龐大的元件樹,並且一個深層巢狀的元件需要從遙遠的祖先元件獲取某些內容。僅使用props,我們可能不得不在整個父鏈上傳遞相同的props

prop drilling diagram

注意,儘管<Footer>元件可能根本不關心這些props,但它仍然需要宣告並傳遞它們,以便<DeepChild>可以訪問它們。如果父鏈更長,那麼在傳遞過程中會有更多元件受到影響。這被稱為“屬性鑽取”,並且處理起來絕對不有趣。

我們可以使用provideinject來解決屬性鑽取問題。父元件可以充當所有其後代元件的依賴提供者。後代樹中的任何元件,無論其深度如何,都可以從其父鏈中的元件中注入依賴項。

Provide/inject scheme

提供

要將資料提供給元件的後代,請使用provide()函式

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

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

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

js
import { provide } from 'vue'

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

provide()函式接受兩個引數。第一個引數稱為注入鍵,可以是字串或Symbol。注入鍵用於後代元件查詢要注入的所需值。單個元件可以使用不同的注入鍵多次呼叫provide()以提供不同的值。

第二個引數是提供的值。值可以是任何型別,包括像refs這樣的響應式狀態。

js
import { ref, provide } from 'vue'

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

提供響應式值允許使用提供的值的後代元件與提供者元件建立響應式連線。

要向元件的後代提供資料,請使用provide選項

js
export default {
  provide: {
    message: 'hello!'
  }
}

對於provide物件中的每個屬性,鍵用於後代元件定位要注入的正確值,而值就是最終被注入的內容。

如果我們需要提供例項級狀態,例如透過data()宣告的資料,那麼provide必須使用函式值

js
export default {
  data() {
    return {
      message: 'hello!'
    }
  },
  provide() {
    // use function syntax so that we can access `this`
    return {
      message: this.message
    }
  }
}

但是請注意,這不會使注入變得響應式。我們將在下面討論如何使注入響應式

應用級提供

除了在元件中提供資料外,我們還可以在應用級別提供

js
import { createApp } from 'vue'

const app = createApp({})

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

應用級提供適用於應用中渲染的所有元件。這在編寫外掛時特別有用,因為外掛通常無法使用元件提供值。

注入

要注入祖先元件提供的資料,請使用inject()函式

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

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

如果提供的值是ref,它將被原樣注入,並且將不會被自動解包。這允許注入元件保留與提供者元件的響應式連線。

帶響應性的完整提供+注入示例

再次提醒,如果不使用<script setup>,則應在setup()內部同步呼叫inject()

js
import { inject } from 'vue'

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

要注入由父元件提供的資料,請使用 inject 選項

js
export default {
  inject: ['message'],
  created() {
    console.log(this.message) // injected value
  }
}

注入是在元件自身的狀態解析之前完成的,因此您可以在 data() 中訪問注入的屬性

js
export default {
  inject: ['message'],
  data() {
    return {
      // initial data based on injected value
      fullMessage: this.message
    }
  }
}

完整的 provide + inject 示例

注入別名

當使用 inject 的陣列語法時,注入的屬性使用相同的鍵暴露在元件例項上。在上面的示例中,屬性在鍵 "message" 下提供,並注入為 this.message。本地鍵與注入鍵相同。

如果我們想使用不同的本地鍵注入屬性,我們需要使用 inject 選項的物件語法

js
export default {
  inject: {
    /* local key */ localMessage: {
      from: /* injection key */ 'message'
    }
  }
}

在這裡,元件將定位到使用鍵 "message" 提供的屬性,並將其暴露為 this.localMessage

注入預設值

預設情況下,inject 假設注入鍵在父鏈的某個地方提供。如果鍵沒有提供,將會有執行時警告。

如果我們想使注入屬性與可選提供者一起工作,我們需要宣告一個預設值,類似於 props

js
// `value` will be "default value"
// if no data matching "message" was provided
const value = inject('message', 'default value')

在某些情況下,預設值可能需要透過呼叫函式或建立一個新的類來建立。為了避免在可選值未使用時產生不必要的計算或副作用,我們可以使用工廠函式來建立預設值

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

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

js
export default {
  // object syntax is required
  // when declaring default values for injections
  inject: {
    message: {
      from: 'message', // this is optional if using the same key for injection
      default: 'default value'
    },
    user: {
      // use a factory function for non-primitive values that are expensive
      // to create, or ones that should be unique per component instance.
      default: () => ({ name: 'John' })
    }
  }
}

處理響應性

當使用響應式 provide / inject 值時,建議儘可能在 provider 內部進行任何對響應式狀態的修改。這確保了提供的狀態及其可能的修改位於同一組件中,便於未來的維護。

有時我們需要從注入元件更新資料。在這種情況下,我們建議提供一個負責修改狀態的函式

vue
<!-- inside provider component -->
<script setup>
import { provide, ref } from 'vue'

const location = ref('North Pole')

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

provide('location', {
  location,
  updateLocation
})
</script>
vue
<!-- in injector component -->
<script setup>
import { inject } from 'vue'

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

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

最後,如果您想確保透過 provide 傳遞的資料不能被注入元件修改,可以將提供的值包裹在 readonly()

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

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

為了使注入與提供者響應性連結,我們需要使用 computed() 函式提供一個計算屬性

js
import { computed } from 'vue'

export default {
  data() {
    return {
      message: 'hello!'
    }
  },
  provide() {
    return {
      // explicitly provide a computed property
      message: computed(() => this.message)
    }
  }
}

帶響應性的完整提供+注入示例

computed() 函式通常用於 Composition API 元件,但也可以用於補充 Options API 的某些用例。您可以透過閱讀 響應性基礎計算屬性(API 偏好設定為 Composition API)來了解更多關於其使用的資訊。

處理符號鍵

到目前為止,我們在示例中一直使用字串注入鍵。如果您正在處理一個具有許多依賴提供者的大型應用程式,或者您正在編寫將被其他開發者使用的元件,最好使用符號注入鍵以避免潛在的衝突。

建議在專用檔案中匯出符號

js
// keys.js
export const myInjectionKey = Symbol()
js
// in provider component
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'

provide(myInjectionKey, {
  /* data to provide */
})
js
// in injector component
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'

const injected = inject(myInjectionKey)

另請參閱: 型別 Provide / Inject

js
// in provider component
import { myInjectionKey } from './keys.js'

export default {
  provide() {
    return {
      [myInjectionKey]: {
        /* data to provide */
      }
    }
  }
}
js
// in injector component
import { myInjectionKey } from './keys.js'

export default {
  inject: {
    injected: { from: myInjectionKey }
  }
}
Provide / Inject 已載入