Vue3 provide/inject用法总结

1. 基本概念

provide/inject 是 Vue3 中实现跨层级组件通信的方案,类似于 React 的 Context。它允许父组件向其所有子孙组件注入依赖,无论层级有多深。

1.1 基本语法

vue 复制代码
// 提供方(父组件)
const value = ref('hello')
provide('key', value)

// 注入方(子孙组件)
const value = inject('key')

2. 基础用法

2.1 提供静态值

vue 复制代码
<!-- Parent.vue -->
<script setup>
import { provide } from 'vue'

// 提供静态值
provide('theme', 'dark')
provide('language', 'zh-CN')
</script>

<!-- Child.vue -->
<script setup>
import { inject } from 'vue'

// 注入值
const theme = inject('theme')
const language = inject('language')
</script>

<template>
  <div :class="theme">
    <p>Current Language: {{ language }}</p>
  </div>
</template>

2.2 提供响应式数据

vue 复制代码
<!-- Parent.vue -->
<script setup>
import { provide, ref } from 'vue'

const count = ref(0)
const updateCount = () => {
  count.value++
}

// 提供响应式数据和更新方法
provide('count', {
  count,
  updateCount
})
</script>

<!-- Child.vue -->
<script setup>
import { inject } from 'vue'

const { count, updateCount } = inject('count')
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="updateCount">Increment</button>
  </div>
</template>

3. 进阶用法

3.1 使用 Symbol 作为 key

ts 复制代码
// injection-keys.ts
export const COUNT_KEY = Symbol('count')
export const THEME_KEY = Symbol('theme')
vue 复制代码
<!-- Parent.vue -->
<script setup lang="ts">
import { provide } from 'vue'
import { COUNT_KEY, THEME_KEY } from './injection-keys'

const count = ref(0)
provide(COUNT_KEY, count)
provide(THEME_KEY, 'dark')
</script>

<!-- Child.vue -->
<script setup lang="ts">
import { inject } from 'vue'
import { COUNT_KEY, THEME_KEY } from './injection-keys'

const count = inject(COUNT_KEY)
const theme = inject(THEME_KEY)
</script>

3.2 提供默认值

vue 复制代码
<script setup>
import { inject } from 'vue'

// 使用静态默认值
const theme = inject('theme', 'light')

// 使用工厂函数作为默认值
const now = inject('timestamp', () => Date.now())
</script>

3.3 只读数据

vue 复制代码
<!-- Parent.vue -->
<script setup>
import { provide, ref, readonly } from 'vue'

const count = ref(0)
// 提供只读版本,防止子组件修改
provide('count', readonly(count))

// 提供更新方法
provide('updateCount', () => {
  count.value++
})
</script>

<!-- Child.vue -->
<script setup>
import { inject } from 'vue'

const count = inject('count')
const updateCount = inject('updateCount')
</script>

4. 实际应用场景

4.1 主题系统

vue 复制代码
<!-- ThemeProvider.vue -->
<script setup>
import { provide, ref } from 'vue'

const theme = ref('light')
const toggleTheme = () => {
  theme.value = theme.value === 'light' ? 'dark' : 'light'
}

provide('theme', {
  theme,
  toggleTheme
})
</script>

<template>
  <div :class="theme.value">
    <slot></slot>
  </div>
</template>

<!-- 使用组件 -->
<script setup>
import { inject } from 'vue'

const { theme, toggleTheme } = inject('theme')
</script>

<template>
  <button @click="toggleTheme">
    Switch to {{ theme === 'light' ? 'dark' : 'light' }} mode
  </button>
</template>

4.2 多语言系统

vue 复制代码
<!-- I18nProvider.vue -->
<script setup>
import { provide, ref } from 'vue'

const locale = ref('en')
const messages = {
  en: {
    greeting: 'Hello',
    farewell: 'Goodbye'
  },
  zh: {
    greeting: '你好',
    farewell: '再见'
  }
}

const t = (key) => messages[locale.value][key]
const setLocale = (lang) => {
  locale.value = lang
}

provide('i18n', {
  locale,
  t,
  setLocale
})
</script>

<!-- 使用组件 -->
<script setup>
import { inject } from 'vue'

const { t, setLocale, locale } = inject('i18n')
</script>

<template>
  <div>
    <select v-model="locale">
      <option value="en">English</option>
      <option value="zh">中文</option>
    </select>
    <p>{{ t('greeting') }}</p>
  </div>
</template>

4.3 状态管理

vue 复制代码
<!-- Store.vue -->
<script setup>
import { provide, reactive } from 'vue'

const store = reactive({
  user: null,
  todos: [],
  addTodo(text) {
    this.todos.push({ id: Date.now(), text, completed: false })
  },
  toggleTodo(id) {
    const todo = this.todos.find(t => t.id === id)
    if (todo) {
      todo.completed = !todo.completed
    }
  }
})

provide('store', store)
</script>

<!-- TodoList.vue -->
<script setup>
import { inject } from 'vue'

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

<template>
  <div>
    <input
      v-model="newTodo"
      @keyup.enter="store.addTodo(newTodo)"
    >
    <ul>
      <li
        v-for="todo in store.todos"
        :key="todo.id"
        @click="store.toggleTodo(todo.id)"
      >
        {{ todo.text }}
      </li>
    </ul>
  </div>
</template>

5. TypeScript 支持

5.1 类型定义

ts 复制代码
// types.ts
export interface ThemeContext {
  theme: Ref<'light' | 'dark'>
  toggleTheme: () => void
}

export const ThemeSymbol = Symbol('theme')

5.2 带类型的 provide/inject

vue 复制代码
<script setup lang="ts">
import { provide, inject } from 'vue'
import { ThemeContext, ThemeSymbol } from './types'

// 提供方
provide<ThemeContext>(ThemeSymbol, {
  theme: ref('light'),
  toggleTheme: () => { /* ... */ }
})

// 注入方
const theme = inject<ThemeContext>(ThemeSymbol)
</script>

6. 最佳实践

  1. 使用 Symbol 作为 key

    ts 复制代码
    const MyKey = Symbol('my-key')
    provide(MyKey, value)
  2. 提供只读数据

    ts 复制代码
    provide('data', readonly(data))
  3. 集中管理 injection key

    ts 复制代码
    // keys.ts
    export const THEME_KEY = Symbol('theme')
    export const I18N_KEY = Symbol('i18n')
  4. 使用组合式函数封装

    ts 复制代码
    // useTheme.ts
    export function useTheme() {
      const theme = inject(THEME_KEY)
      if (!theme) {
        throw new Error('useTheme must be used within ThemeProvider')
      }
      return theme
    }

7. 注意事项

  1. 响应性

    • 确保提供响应式数据时使用 ref 或 reactive
    • 注意数据的可变性和只读性
  2. 默认值

    • 提供合理的默认值
    • 考虑使用工厂函数作为默认值
  3. 类型安全

    • 使用 TypeScript 定义接口
    • 使用 Symbol 作为 key
  4. 性能考虑

    • 避免提供过大的数据结构
    • 合理划分提供的数据范围

通过合理使用 provide/inject,我们可以有效地管理跨组件通信,构建可维护的组件树。但要注意避免过度使用,以免造成数据流向难以追踪的问题。

相关推荐
Frankabcdefgh6 分钟前
前端面试 js
开发语言·javascript·原型模式
浏览器爱好者16 分钟前
如何删除Google Chrome中的所有历史记录【一键清除】
前端·chrome
埃兰德欧神17 分钟前
三分钟让你的H5变身‘伪原生’,揭秘H5秒变应用的魔法配置
javascript·html·产品
米开朗基杨18 分钟前
Cursor 最强竞争对手来了,专治复杂大项目,免费一个月
前端·后端
Lonwayne19 分钟前
Web服务器技术选型指南:主流方案、核心对比与策略选择
运维·服务器·前端·程序那些事
学习机器不会机器学习26 分钟前
深入浅出JavaScript常见设计模式:从原理到实战(1)
开发语言·javascript·设计模式
hax30 分钟前
deepseek-R1 理解代码能力一例
javascript·deepseek
brzhang32 分钟前
效率神器!TmuxAI:一款无痕融入终端的AI助手,让我的开发体验翻倍提升
前端·后端·算法
海底火旺34 分钟前
JavaScript 原型链检查:从 `instanceof` 到 `isPrototypeOf` 的演进
前端·javascript·面试
埃兰德欧神34 分钟前
Lynx:革新跨端开发,一次编写,多端闪耀
前端·javascript·前端框架