Vue3 + TypeScript provide/inject 小白学习笔记

一、这是什么?为什么需要?

1.1 简单理解

  • provide(提供):爷爷组件说:"我这里有数据,子孙们随便用!"

  • inject(注入):孙子组件说:"我要用爷爷提供的数据!"

1.2 解决的问题

没有 provide/inject 时

text

复制代码
爷爷组件 → 爸爸组件 → 儿子组件 → 孙子组件
    ↓         ↓         ↓         ↓
  数据      props     props     props (需要层层传递,好麻烦!)

使用 provide/inject 后

text

复制代码
爷爷组件(provide数据)
    ↓
孙子组件(inject数据)直接跳过中间商!

二、基础使用方法

2.1 提供数据(爷爷组件)

vue

复制代码
<template>
  <div>
    <ParentComponent />
  </div>
</template>

<script setup lang="ts">
import { ref, provide } from 'vue'
import ParentComponent from './ParentComponent.vue'

// 1. 准备要提供的数据
const userInfo = ref({
  name: '张三',
  age: 25
})

const theme = ref('dark')

// 2. 提供数据给子孙组件
provide('userInfo', userInfo)
provide('theme', theme)
</script>

2.2 注入数据(孙子组件)

vue

复制代码
<template>
  <div>
    <h1>用户信息:{{ userInfo.name }}</h1>
    <p>主题:{{ theme }}</p>
  </div>
</template>

<script setup lang="ts">
import { inject } from 'vue'

// 注入爷爷提供的数据
const userInfo = inject('userInfo')
const theme = inject('theme')
</script>

三、TypeScript 类型安全写法

3.1 基础类型定义

vue

复制代码
<script setup lang="ts">
import { ref, provide, inject } from 'vue'

// 定义接口
interface User {
  name: string
  age: number
  email?: string
}

// 提供数据时指定类型
const userInfo = ref<User>({
  name: '李四',
  age: 30
})

provide('userInfo', userInfo)

// 注入时指定类型
const userInfo = inject<User>('userInfo')
</script>

3.2 更安全的写法(推荐)

vue

复制代码
<script setup lang="ts">
import { inject, ref, provide } from 'vue'

interface Theme {
  mode: 'light' | 'dark'
  primaryColor: string
}

// 提供默认值,避免 undefined
const defaultTheme: Theme = {
  mode: 'light',
  primaryColor: '#1890ff'
}

const theme = ref<Theme>(defaultTheme)
provide('theme', theme)

// 注入时提供默认值
const theme = inject<Theme>('theme', defaultTheme)

3.3 使用 Symbol 避免命名冲突(高级用法)

ts

复制代码
// types.ts 或 constants.ts
export const THEME_KEY = Symbol('theme') as InjectionKey<Theme>
export const USER_KEY = Symbol('user') as InjectionKey<User>

// 爷爷组件
import { THEME_KEY, USER_KEY } from './types'
provide(THEME_KEY, theme)
provide(USER_KEY, userInfo)

// 孙子组件
const theme = inject(THEME_KEY)
const userInfo = inject(USER_KEY)

四、实际项目完整示例

4.1 用户信息共享

vue

复制代码
<!-- App.vue (根组件) -->
<template>
  <div :class="`app ${theme}`">
    <Header />
    <Content />
  </div>
</template>

<script setup lang="ts">
import { ref, provide } from 'vue'
import Header from './Header.vue'
import Content from './Content.vue'

interface AppState {
  user: {
    name: string
    role: string
  }
  theme: 'light' | 'dark'
  isLogin: boolean
}

const appState = ref<AppState>({
  user: {
    name: '王五',
    role: 'admin'
  },
  theme: 'light',
  isLogin: true
})

// 提供整个应用状态
provide('appState', appState)

// 提供修改主题的方法
const toggleTheme = () => {
  appState.value.theme = appState.value.theme === 'light' ? 'dark' : 'light'
}
provide('toggleTheme', toggleTheme)
</script>

<style>
.light { background: white; color: black; }
.dark { background: #333; color: white; }
</style>

vue

复制代码
<!-- Header.vue (头部组件) -->
<template>
  <header>
    <h1>欢迎,{{ appState.user.name }}</h1>
    <button @click="toggleTheme">
      切换主题:{{ appState.theme }}
    </button>
  </header>
</template>

<script setup lang="ts">
import { inject } from 'vue'

interface AppState {
  user: {
    name: string
    role: string
  }
  theme: 'light' | 'dark'
  isLogin: boolean
}

// 注入数据和方法
const appState = inject<AppState>('appState')!
const toggleTheme = inject<() => void>('toggleTheme')!
</script>

五、响应式数据更新

5.1 响应式原理

vue

复制代码
<script setup lang="ts">
import { ref, provide } from 'vue'

// 使用 ref 创建响应式数据
const count = ref(0)

// 提供响应式数据
provide('count', count)

// 在任意组件中修改,所有注入的地方都会更新
const increase = () => {
  count.value++  // 修改这里,所有注入 count 的组件都会更新
}
provide('increase', increase)
</script>

5.2 在子组件中修改

vue

复制代码
<template>
  <div>
    <p>计数:{{ count }}</p>
    <button @click="increase">+1</button>
  </div>
</template>

<script setup lang="ts">
import { inject } from 'vue'

const count = inject<number>('count')!
const increase = inject<() => void>('increase')!
</script>

六、常见问题及解决方法

6.1 问题1:inject 返回 undefined

错误

ts

复制代码
const data = inject('someKey') // 可能是 undefined
console.log(data.value) // 报错!

解决

ts

复制代码
// 方法1:提供默认值
const data = inject('someKey', defaultValue)

// 方法2:类型断言
const data = inject('someKey')!

// 方法3:运行时检查
const data = inject('someKey')
if (!data) {
  throw new Error('数据未提供')
}

6.2 问题2:类型不匹配

错误

ts

复制代码
const user = inject('user') // 类型是 any,不安全

解决

ts

复制代码
interface User {
  name: string
  age: number
}

// 明确指定类型
const user = inject<User>('user', { name: '', age: 0 })

6.3 问题3:修改了非响应式数据

错误

ts

复制代码
// 爷爷组件
provide('staticData', { count: 0 }) // 不是响应式的!

// 孙子组件
const data = inject('staticData')
data.count++ // 修改了,但不会触发更新

解决

ts

复制代码
// 使用 ref 或 reactive 创建响应式数据
const staticData = ref({ count: 0 })
provide('staticData', staticData)

七、最佳实践

7.1 代码组织建议

ts

复制代码
// useProvide.ts - 抽离 provide 逻辑
import { ref, provide } from 'vue'

export function useProvideAppState() {
  const appState = ref({
    user: { name: '', role: '' },
    theme: 'light'
  })
  
  const updateUser = (user: User) => {
    appState.value.user = user
  }
  
  const toggleTheme = () => {
    appState.value.theme = appState.value.theme === 'light' ? 'dark' : 'light'
  }
  
  provide('appState', appState)
  provide('updateUser', updateUser)
  provide('toggleTheme', toggleTheme)
  
  return { appState, updateUser, toggleTheme }
}

7.2 在组件中使用

vue

复制代码
<script setup lang="ts">
// App.vue
import { useProvideAppState } from './useProvide'

useProvideAppState()
</script>

八、什么时候用 provide/inject?

✅ 适合的场景:

  1. 主题切换(深色/浅色模式)

  2. 用户登录信息(用户名、权限)

  3. 多语言国际化

  4. 全局配置

  5. 复杂表单(多个子组件需要访问表单数据)

❌ 不适合的场景:

  1. 父子组件通信(用 props/emit)

  2. 简单的状态共享(用 Pinia 更好)

  3. 非响应式数据(用模块导出更好)

九、总结

核心要点:

  1. 爷爷 provide,孙子 inject - 跳过中间组件

  2. 记得用 ref/reactive - 保证数据是响应式的

  3. TypeScript 类型安全 - 注入时指定类型和默认值

  4. Symbol 避免冲突 - 大型项目推荐使用

最简单的记忆:

ts

复制代码
// 提供数据
const data = ref('你好')
provide('key', data)

// 使用数据
const data = inject('key')

这样就能在任意层级的组件中共享数据了!

相关推荐
To_OC6 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC6 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic6 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy6 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志6 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble8 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长8 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯8 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜9 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司9 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能