如何在typescript中为交集类型赋值

ztmd8pv5  于 2023-06-24  发布在  TypeScript
关注(0)|答案(1)|浏览(123)

我试图在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;}'。属性“pid”在类型“ChildProcess”中是可选的,但在类型“{ pid:number; }'
函数terminate期望“交集类型

ChildProcess & {
    pid: number;
}

但是我目前只有一个ChildProcess,我如何将ChildProcess转换为ChildProcess & { pid: number;}
我查了ChildProcess,它有

readonly pid?: number | undefined;

所以对我来说,它似乎可以转换为ChildProcess & { pid: number;},但typescript编译器说:
属性“pid”在类型“ChildProcess”中是可选的,但在类型“{ pid:number; }'
我该怎么做这个转换?

brccelvz

brccelvz1#

terminate(vlsProcess)编译时没有错误的方法是说服编译器vlsProcessChildProcess & { pid: number; }类型,这意味着它必须是ChildProcess,其中pid属性已知存在,并且类型为number。但是vlsProcess被声明为ChildProcess,其pid属性是可选的。所以你需要对pid属性做一些事情。
一种方法是在调用terminate(vlsProcess)之前写一个typeof vlsProcess.pid === "number"的检查,希望这样的检查能够narrowvlsProcess的明显类型。不幸的是,这不起作用:

if (typeof vlsProcess.pid === "number") {
  terminate(vlsProcess); // same error 😢
}

虽然检查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<K, {} | null>,使用Record实用类型。已知{ [P in K]: {} | null }类型在K类型的每个键上都有{} | null类型的属性,而{} | null基本上允许除了undefined之外的所有值。将类型与{} | null相交将有助于从其域中消除undefined,如TypeScript 4.8中引入的那样。
请注意,该实现使用类型Assertobj 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链接到代码

相关问题