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: stringage: numberemail?: 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: stringrole: 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: stringrole: 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: stringage: 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?
✅ 适合的场景:
主题切换(深色/浅色模式)
用户登录信息(用户名、权限)
多语言国际化
全局配置
复杂表单(多个子组件需要访问表单数据)
❌ 不适合的场景:
父子组件通信(用 props/emit)
简单的状态共享(用 Pinia 更好)
非响应式数据(用模块导出更好)
九、总结
核心要点:
爷爷 provide,孙子 inject - 跳过中间组件
记得用 ref/reactive - 保证数据是响应式的
TypeScript 类型安全 - 注入时指定类型和默认值
Symbol 避免冲突 - 大型项目推荐使用
最简单的记忆:
ts
// 提供数据 const data = ref('你好') provide('key', data)// 使用数据 const data = inject('key')
这样就能在任意层级的组件中共享数据了!