Typescript相等运算符与Array.includes检查



我已经准备好了演示(复制链接(

这是我的问题:

interface Person {
target:
| "first_name"
| "last_name"
| "date_of_birth"
| "address_line"
| "address_line2"
| "city"
| "state"
| "zip";
}
interface Address {
address_line?: string | null;
address_line2?: string | null;
state?: string | null;
city?: string | null;
zip?: string | null;
}
interface Owner {
id: number;
first_name: string;
last_name: string;
date_of_birth: string | null;
address: Address
}
function createNewOnwer(target: Person["target"]) {
const newData = {} as Partial<Owner>;
if (
target === "address_line" ||
target === "address_line2" ||
target === "zip" ||
target === "state" ||
target === "city"
) {
// hover on target
newData.address = {
[target]: "test address" // typescript knows exactelly that target here is one of "address_line" | "address_line2" | "zip" | "state" | "city"
};
} else {
newData[target] = String("test address"); // and here one of "first_name" | "last_name" | "date_of_birth"
}
}
function createNewOnwerWithIncludeCheck(target: Person["target"]) {
const newData = {} as Partial<Owner>;
if (
["address_line", "address_line2", "zip", "state", "city"].includes(target)
) {
// hover on target
newData.address = {
[target]: "test address" // ⬅️ why it doesn't know here that it is still one of  "address_line" | "address_line2" | "zip" | "state" | "city" ??? 🤔
};
} else {
newData[target] = String("test address"); // Why typescript throws error here?
}
}
function createNewOnwerWithIncludeCheckWithHardcodedTypes(target: Person["target"]) {
const newData = {} as Partial<Owner>;
if (
["address_line", "address_line2", "zip", "state", "city"].includes(target)
) {
// hover on target
newData.address = {
[target as "address_line" | "address_line2" | "zip" | "state" | "city"]: "test address" // still thinks it is one of all Person["target"]
};
} else {
newData[target as "first_name" | "last_name" | "date_of_birth"] = String("test address");
}
}
  1. 为什么当我对if中的字符串使用===检查时,typescript能够区分类型,并且确切地知道要考虑哪些变体?但当我在if中使用Array.includes作为检查时,它不是吗?

  2. 如何在if中使用Array.includes检查?

target === "address_line"这样的一系列相等检查之所以适用,是因为相等检查是TypeScript用来缩小被检查表达式类型的构造之一。也就是说,x === y可以充当xy上的类型的保护

通常,函数调用或方法调用可以充当类型保护,但要做到这一点,需要对它们进行注释,以便它们的返回类型是形式为arg is Type的类型谓词,其中arg是函数参数(或this(的名称,而Type是比typeof arg窄的某种类型。但是Array.prototype.includes()方法的标准TypeScript库类型没有以这种方式进行注释。这不是一个类型保护功能。

为什么不呢?这是在microsoft/TypeScript#36275上提出的,但这个建议被拒绝了,因为它太复杂了,无法正确执行。很多时候,人们称array.includes(value)而不试图缩小value的类型。如果他们确实想要,他们不一定会对相反的结果感到满意,其中!array.includes(value)意味着value而不是数组元素的类型。如果您有一个像["a", "b", "c"]这样的字符串数组,那么"z"不在数组中并不意味着"z"不是string(!(。因此,需要注意实现一种不会产生非常奇怪效果的类型保护。


您在这里的选择是:

您可以编写自己的独立类型保护函数,在函数内部调用Array.prototype.includes(),但不会影响编译器对Array.prototype.includes()的总体看法。这是最安全的做法,因为您可以控制何时调用类型保护函数:

function includes<T extends string>(arr: readonly T[], val: string): val is T {
return (arr as readonly string[]).includes(val);
}
function createNewOnwerWithIncludeCheck(target: Person["target"]) {
const newData: Partial<Owner> = {};
if (includes(["address_line", "address_line2", "zip", "state", "city"], target)) {
newData.address = { [target]: "test address" };
} else {
newData[target] = String("test address"); // okay
}
}

因此,includes()是一个独立的函数,它接受字符串数组arr和目标字符串val,并返回val is T,其中T是从arr的内容推断出的字符串文字类型的并集。

您可以看到它按要求工作。


或者,您可以决定为Array.prototype.includes()定义一个调用签名,使其充当类型保护,并将其合并到您的代码库中:

// declare global { // uncomment this if your code is in a module
interface ReadonlyArray<T> {
includes<U extends (string extends U ? never : string)>(
this: ReadonlyArray<U>, val: string): val is U;
}
interface Array<T> {
includes<U extends (string extends U ? never : string)>(
this: Array<U>, val: string): val is U;
}
// }

这将使得在任何时候调用array.includes(value),其中array的元素是string的子类型U(但至关重要的是不是string本身(,并且其中valuestring,那么true结果意味着valueU类型,而false结果意味着,valueU类型的而不是。这相当复杂(这就是为什么他们没有在microsoft/TypeScript#36275中这样做(,而且可能很脆弱,所以你可能需要确保它不会给其他地方的代码库带来问题。

无论如何,这将使您的代码或多或少地按原样运行

function createNewOnwerWithIncludeCheck2(target: Person["target"]) {
const newData: Partial<Owner> = {};
if ((["address_line", "address_line2", "zip", "state", "city"] as const).includes(target)) {
newData.address = { [target]: "test address" };
} else {
newData[target] = String("test address"); // okay
}
}

除了数组文字需要const断言(["a", "b", "c"] as const(,编译器才能意识到该数组的类型应该比string[]窄。string[]数组对缩小范围没有帮助。

将其合并是你可能最接近你想要的状态,在那里你的原始代码";只要工作";,但我会远离它,更喜欢独立类型的保护功能。有人在代码的其他部分调用includes(arr, val)的可能性很小,而Array.prototype.includes()更有可能在其他地方使用。

游乐场链接到代码

最新更新