◆

Vue3 InjectionKey 为 provide/inject 提供类型

2023-02-02 01:370

用 provide / inject 做跨层级传值时,如果 key 用的是普通字符串,TypeScript 是帮不上忙的。inject('user') 的返回类型只能是 unknown,我们要么手动断言,要么写成 any 一路裸奔——而一旦提供方改了数据结构,所有消费方都不会报错,问题只能等运行时才暴露。

Vue 为此提供了 InjectionKey,让 key 自己携带类型信息。提供方和消费方共用同一个 key,类型就自动对齐了。

other

Vue 提供了一个 InjectionKey 接口,该接口是扩展 Symbol 的泛型类型。它可用于在提供者和消费者之间同步 inject 值的类型:

它的定义很简单,本质就是 interface InjectionKey<T> extends Symbol {}。泛型参数 T 只在类型层面存在,运行时它就是一个普通 Symbol,不产生任何额外开销。provide 和 inject 的类型声明都对这个泛型做了识别,于是形成一条闭环:provide 时值的类型必须和 T 匹配,inject 时返回类型直接就是 T。

import { InjectionKey, provide, inject } from 'vue';

const key: InjectionKey<string> = Symbol();

provide(key, 'foo'); // 提供非字符串值将导致错误

const foo = inject(key); // foo 的类型: string | undefined

用 Symbol 而不是字符串做 key,还顺手解决了另一个问题:命名冲突。字符串 key 是全局共享的,两个互不相关的组件树各自 provide('config'),中间某一层就可能意外拦截到不属于它的值,而且这种错误极难定位。Symbol 天生唯一,不存在撞名的可能。

返回类型里那个 | undefined 不是多余的。inject 找不到对应的提供方时会返回 undefined,TS 如实反映了这一点。想去掉它有两个办法:给 inject 传第二个参数作为默认值,此时返回类型收窄为 T;或者确信一定有提供方,用非空断言:

// 提供默认值,返回类型为 string
const foo = inject(key, 'default');

// 确信一定存在,用非空断言
const bar = inject(key)!;

字符串 key 的写法

如果使用字符串 key 或非类型化 symbols,则需要显式声明 inject 值的类型:

const foo = inject < string > 'foo'; // string | undefined

上面这行是 markdown 高亮把泛型语法拆散了,实际要写的是 inject<string>('foo')——把类型作为泛型参数手动传给 inject。这种写法能用,但类型是「声称」的而不是「约束」的:提供方那边写 provide('foo', 123) 传个数字进去,TS 一点意见都没有,消费方却依然以为自己拿到的是 string。所以它只是补救措施,不如 InjectionKey 可靠。

实践建议

InjectionKey 的定义应该单独放一个文件,让提供方和消费方都从这里 import,避免各自重复声明。配上一个描述性的 Symbol 参数,调试时在控制台里能看清是哪个 key:

// keys.ts
import type { InjectionKey, Ref } from 'vue';

export interface UserInfo {
  id: number;
  name: string;
}

export const userKey: InjectionKey<Ref<UserInfo>> = Symbol('user');
export const updateUserKey: InjectionKey<(u: UserInfo) => void> =
  Symbol('updateUser');

注意泛型里写的是 Ref<UserInfo> 而不是 UserInfo。因为要保持响应性,provide 传出去的通常是 ref 本身而不是 .value,类型也得如实反映这一层包装,否则消费方访问 .value 时 TS 会报错。修改方法同样值得单独定义一个 key 传下去,这比让子组件直接改注入值要可控得多。

💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEVue3 InjectionKey 为 provide/inject 提供类型加载中