跳转到内容

提供/注入

本页面假设您已经阅读了组件基础。如果是初次接触组件,请先阅读。

属性钻取

通常,当我们需要从父组件传递数据到子组件时,我们会使用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 已加载