我可以编写一个断言多个不变量的类型保护吗?

Clé*_*ent 8 nullable invariants typescript

我可以编写一个类型保护来断言一个参数的一个或多个子对象吗?在伪代码中,它可能如下所示:

class C {
    a: number?;
    b: string?;

    function assertInitialized() : (this.a is number) and (this.b is string) {
        return this.a !== null && this.b !== null;
    }
}
Run Code Online (Sandbox Code Playgroud)


背景:我通常使用一个函数来检查我的类的不变量;例如,假设我有一个包含一些可空字段的类,这些字段被异步初始化。

class DbClient {
    dbUrl: string;
    dbName: string;
    dbConnection: DBConnection?;
    …
}
Run Code Online (Sandbox Code Playgroud)

此类经过复杂的异步初始化过程,之后dbConnection变为非空。在初始化 dbConnection 之前,类的用户不得调用某些方法,所以我有一个函数assertReady

assertReady() {
    if (this.dbConnection === null) {
        throw "Connection not yet established!";
    }
}
Run Code Online (Sandbox Code Playgroud)

这个函数assertReady在每个需要DbClient完全初始化的函数开始时调用,但我仍然要写非空断言:

fetchRecord(k: string) {
    assertReady();
    return this.dbConnection!.makeRequest(/* some request based on k */);
}
Run Code Online (Sandbox Code Playgroud)

我可以提供assertReady一个!不需要的签名吗? 我不想传递this.dbConnectionassertReady,因为该函数通常更复杂。

我知道的唯一技巧是创建一个与当前类具有相同字段但具有不可为空类型(没有?)的接口。然后我可以制作一个这样的类型保护this is InitializedDbClient。不幸的是,这需要复制大部分类定义。有没有更好的办法?

Alu*_*dad 7

是的,你可以,而且你几乎在伪代码中完全正确

Interface A {
  a?: number;
  b?: string;

  hasAandB(): this is {a: number} & {b: string};
}
Run Code Online (Sandbox Code Playgroud)

注意你的伪代码是如何and变成&. 确实非常接近。

当然,在这种情况下,没有必要使用该运算符,即类型交集运算符,因为我们可以将其简化为

hasAandB(): this is {a: number, b: string};
Run Code Online (Sandbox Code Playgroud)

但是想象一下,我们添加了第三个属性,比如c,它不受类型保护的影响,但我们仍然不想失去它对结果类型的贡献。

你对可组合类型守卫的直觉让我们回到了原点

hasAandB(): this is this & {a: number, b: string};
Run Code Online (Sandbox Code Playgroud)

您可以使用这些模式做各种非常有趣且非常有用的事情。

例如,您可以根据对象的类型一般地传递​​许多属性键,并且根据您传递的实际键,结果可以被类型保护到每个属性的承载的交集。

function hasProperties<T, K1 extends keyof T, K2 extends keyof T>(
  x: Partial<T>,
  key1: K1,
  key2: K2
): x is Partial<T> & {[P in K1 | K2]: T[P]} {
  return key1 in x && key2 in x;
}


interface I {
  a: string;
  b: number;
  c: boolean;
}

declare let x: Partial<I>;

if (hasProperties(x, 'a', 'b')) {...}
Run Code Online (Sandbox Code Playgroud)

事实上,这只是触及了可能的表面。

另一个非常有趣的应用是定义任意泛型和类型安全的构建器和可组合工厂。