vscode 扩展 - 如何在 TypeScript 中为交叉类型分配值
我正在尝试用 TypeScript 编写一个 VSCode 扩展,我有一个 ChildProcess,我想终止/杀掉它。
export declare function terminate(process: ChildProcess & {
pid: number;
}, cwd?: string): boolean;
...
let vlsProcess: ChildProcess
然后我尝试打电话
terminate(vlsProcess);
但是我有这个错误:
类型为 'ChildProcess' 的参数不可分配给类型为 'ChildProcess & { pid: number; }' 的参数。类型 'ChildProcess' 不能分配给类型 '{ pid: number; }'。类型 'ChildProcess' 中的属性 'pid' 是可选的,但在类型 '{ pid: number; }' 中是必需的
函数 terminate 期望一个“交叉类型”
ChildProcess & {
pid: number;
}
但我目前只有一个 ChildProcess,我如何将 ChildProcess 转换为 ChildProcess & { pid: number;}?
我检查了 ChildProcess,它有
readonly pid?: number | undefined;
所以在我看来,它似乎可以转换为 ChildProcess & { pid: number;},但是 TypeScript 编译器却报错说:
在类型 'ChildProcess' 中,属性 'pid' 是可选的,但在类型 '{ pid: number; }' 中是必需的
我怎样才能进行这个转换?
答案#1
让 terminate(vlsProcess) 编译而不报错的方法是让编译器相信 vlsProcess 的类型是 ChildProcess & { pid: number; },也就是说,它必须是一个 ChildProcess,且其 pid 属性已知存在并且类型为 number。但是 vlsProcess 被声明为 ChildProcess,其 pid 属性是可选的。所以你需要对该 pid 属性做一些处理。
一种方法是在调用 terminate(vlsProcess) 之前写一个检查 typeof vlsProcess.pid === "number",希望这样的检查能缩小 vlsProcess 的表面类型。不幸的是,这种方法不起作用:
if (typeof vlsProcess.pid === "number") {
terminate(vlsProcess); // same error 😢
}
这本质上是 TypeScript 缺少的一个功能,如 microsoft/TypeScript#42384 所述。虽然检查 typeof vlsProcess.pid === "number" 可以将 vlsProcess.pid 的显式类型从 number | undefined 缩小到 number,但它对 vlsProcess 本身的显式类型没有影响。通常,对某个子属性如 a.b.c.d.e.f 进行检查而让其影响所有父对象的成本会太高,因为编译器需要花时间合成所有相关类型,而大多数类型对于大多数调用来说完全没有用。
除非那里出现更好的情况,否则我们可以幸运地通过实现自定义类型保护函数来模拟这种缩小。像这样:
function hasDefinedProp<T extends object, K extends PropertyKey>(
obj: T, k: K
): obj is T & { [P in K]: {} | null } {
return (typeof (obj as any)[k] !== "undefined");
}
如果 hasDefinedProp(obj, k) 返回 true,那么 obj 将从其原始类型缩小为一个子类型,该子类型被已知在 k 键上有一个已定义的属性。这被表示为 T 和 { [P in K]: {} | null } 的交集,后者使用 Record 工具类型等价于 Record<K, {} | null>。类型 { [P in K]: {} | null } 被已知在类型为 K 的每个键上都有一个类型为 {} | null 的属性,而 {} | null 本质上允许除了 undefined 之外的所有值。将一个类型与 {} | null 相交将起到从其域中消除 undefined 的作用,这在 TypeScript 4.8 中引入。
请注意,该实现使用类型断言 obj as any,以允许我们在不报错的情况下索引 obj[key]。
好,现在让我们试试:
if (hasDefinedProp(vlsProcess, "pid")) {
vlsProcess // ChildProcess & { pid: {} | null }
terminate(vlsProcess); // okay
}
可以的。TypeScript 认为缩小后的类型 ChildProcess & { pid: {} | null } 可以赋值给 ChildProcess & { pid: number },因为前者的 pid 属性是 (number | undefined) & ({} | null),而这就是 number。
如果出于某种原因,hasDefinedProp() 返回 false,那么你就不应该调用 terminate(vlsProcess),因为 vlsProcess 没有 pid。在这种情况下你应该做什么取决于你的使用场景。上面的例子只是跳过了 terminate() 调用,但你可能想抛出一个异常或者做其他处理。
代码的 Playground 链接
