Vue3 + TypeScript中provide和inject的用法示例

基础写法(类型安全)

typescript

复制代码
// parent.component.vue
import { provide, ref } from 'vue'
import type { InjectionKey } from 'vue'

// 1. 定义类型化的 InjectionKey
const COUNTER_KEY = Symbol() as InjectionKey<number>
const USER_KEY = Symbol() as InjectionKey<{ name: string }>

// 在 setup 中使用
setup() {
  const counter = ref(0)
  provide(COUNTER_KEY, counter) // 提供响应式数据
  
  const user = { name: 'John' }
  provide(USER_KEY, user) // 提供静态对象
  
  return { counter }
}

typescript

复制代码
// child.component.vue
import { inject } from 'vue'
import type { InjectionKey } from 'vue'

// 复用相同的 key
const COUNTER_KEY = Symbol() as InjectionKey<number>
const USER_KEY = Symbol() as InjectionKey<{ name: string }>

setup() {
  // 2. 安全注入(带默认值)
  const counter = inject(COUNTER_KEY, ref(0)) // 响应式数据
  const user = inject(USER_KEY, { name: 'Guest' }) // 静态数据
  
  // 3. 强制注入(当确定父级已提供时)
  const forcedCounter = inject(COUNTER_KEY)!
  
  return { counter, user }
}

<script setup> 语法糖写法

vue

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

// 定义 key
const messageKey = Symbol() as InjectionKey<string>

const message = ref('Hello from parent')
provide(messageKey, message)
</script>

vue

复制代码
<!-- Child.vue -->
<script setup lang="ts">
import { inject } from 'vue'

const messageKey = Symbol() as InjectionKey<string>

// 注入 + 类型声明
const message = inject(messageKey, 'default message')

// 处理可能 undefined 的情况
const safeMessage = inject(messageKey) ?? 'fallback value'
</script>

响应式对象注入

typescript

复制代码
// types.ts
export interface User {
  id: number
  name: string
}

export const UserKey = Symbol() as InjectionKey<User>

vue

复制代码
<!-- Parent.vue -->
<script setup lang="ts">
import { provide, reactive } from 'vue'
import { UserKey } from './types'

const user = reactive({
  id: 1,
  name: 'Alice'
})

provide(UserKey, user)
</script>

vue

复制代码
<!-- Child.vue -->
<script setup lang="ts">
import { inject } from 'vue'
import { UserKey } from './types'

const user = inject(UserKey)

// 使用时需要处理可能 undefined 的情况
if (user) {
  console.log(user.name) // 类型安全
}
</script>

最佳实践提醒:

  1. 使用 InjectionKey:确保类型安全

  2. 默认值处理inject(key, defaultValue)

  3. 响应式数据 :建议使用 ref/reactive 保持响应性

  4. 代码组织:推荐将 keys 集中管理在单独文件中

  5. 安全判断 :当不确定是否已提供时,使用可选链操作符 ?.

typescript

复制代码
// 推荐的文件结构
// src/provides/keys.ts
import type { InjectionKey } from 'vue'

export const API_KEY = Symbol() as InjectionKey<AxiosInstance>
export const THEME_KEY = Symbol() as InjectionKey<'light' | 'dark'>

typescript

TypeScript 复制代码
// 祖先组件
// 提供函数给后代,获取受理样品选集
provide("provideApplySampleSelection", handleApplySampleSelectionChange);
// 获取受理样品表格勾选的行数据
const handleApplySampleSelectionChange = (newSelection: ApplySample[]) => {
  //获取勾选行数据
  applySampleTableSelection.value = newSelection;
  // 控制按钮编辑状态
  buttonDisabled();
};

// 提供数据给后代
// 受理样品列表
provide("applySampleList", applySampleTableData); // applySampleTableData是个Ref对象,不需要.value,传递ref对象本身(响应式数据)
// 受理类别
provide(
  "acceptType",
  computed(() => applyBasicInfo.value.acceptType) // applyBasicInfo.value.acceptType 是派生数据(属性数据),通过computed提供响应式数据
);
// 操作指令类型:新增删除:info-add;修改:info-modify;查看:info-view
provide("operateCommandType", ref("info-add"));
// 滚动到离顶部表头的距离
provide("scrollTop", applySampleTableScrollTop); // applySampleTableScrollTop是个Ref对象,不需要.value,传递ref对象本身(响应式数据)


// 后代组件
// 注入祖先提供的函数
const sendApplySampleSelection = inject("provideApplySampleSelection", (data: ApplySample[]) => {});

// 获取受理样品表格勾选的行数据
const handleApplySampleSelectionChange = (newSelection: ApplySample[]) => {
  // 向祖先组件发送表格选集
  sendApplySampleSelection(newSelection);
};

// 接收祖先提供的数据
// 受理样品列表
const applySampleList = inject<Ref<ApplySample[]>>("applySampleList", ref([]));
// 受理类别
const acceptType = inject<Ref<string>>("acceptType", ref(""));
// 操作指令类型:新增删除:info-add;修改:info-modify;查看:info-view
const operateCommandType = inject<Ref<"info-add" | "info-modify" | "info-view">>("operateCommandType", ref("info-add"));
// 滚动到离顶部表头的距离
const scrollTop = inject<Ref<number>>("scrollTop", ref(0));
相关推荐
志尊宝6 分钟前
Vue3 零基础每日笔记(026):emit 子传父——defineEmits 与事件校验
前端·javascript·html·vue3
ssshooter28 分钟前
Node.js 天生就是异步的,为什么还要消息队列?
javascript·后端·面试
mONESY2 小时前
从 RAG 到 Agentic RAG:固定流程的五个毛病,和把它拆成一张图
javascript
梦帮科技2 小时前
从 Guest 到 Platinum:Prompt 配额、API Key 与访问控制的安全闭环
javascript·git·架构·node.js·reactjs·html5·visual studio
linux_cfan2 小时前
videojs v10 源代码系列解读:10 · `DestroyMixin`:双重 rAF 延迟销毁
前端·javascript·音视频
雪芽蓝域zzs2 小时前
第四十六节:顶部【驾驶舱】独立大屏页面实现
前端·javascript·vue.js
右耳朵猫AI3 小时前
Node.js周刊2026W37 | 三处进程崩溃修复、fs 内置 glob、Workers 模块注册表、Vitest 5.0
javascript·后端·node.js
摸鱼仙人~3 小时前
Vue 应用完整启动链路
前端·vue.js·软件工程
右耳朵猫AI3 小时前
Web前端周刊2026W37 | Shopify 转原生、React 编译 Rust 化、Vitest 5.0、Rslib 1.0
前端·javascript·react.js·typescript·node.js
雪芽蓝域zzs4 小时前
第四十八节:驾驶舱大屏进阶:数字滚动动画 + 渐变边框美化
前端·javascript·vue.js