嘿,欢迎阅读我们的 TypeScript Narrowing 系列的另一篇文章。 在这篇文章中,我将解释:
这是我们系列的第三篇文章,如果你还没有看过之前的文章,我强烈建议你去看看,它们为收窄提供了坚实的基础。
类型谓词
在上一篇文章中,我们探讨了基本的类型保护运算符。 现在我想向你展示类型保护函数。
例如,如果您需要检查名为 value 的变量是否为字符串,则可以使用 typeof 运算符。 但你也可以做的是创建一个名为 isString() 的函数,它接收一个参数并在给定参数是字符串时返回 true。
const isString = (value: any): boolean => typeof value === ‘string’;
还记得上一篇文章中的 formatErrorMessage() 函数吗?
const formatErrorMessage = ( value: null | undefined | string | Error | Warning): string => { const prefix = ‘Error: ‘; // If it’s falsy (null, undefined, empty string), return “Unknown” with the prefix if (!value) { return prefix + ‘Unknown’; } // If it’s a string, return the string with the prefix if (typeof value === ‘string’) { return prefix + value; } // If it’s a Warning, return the Warning.text with the prefix if (‘text’ in value) { return prefix + value.text; } // If it’s an Error, return the Error.message with the prefix if (value instanceof Error) { return prefix + value.message; } // We will never reach here throw new Error(`Invalid value type`);};interface Warning { text: string;}
让我们从中删除 typeof 运算符并使用 isString() 代替。
const formatErrorMessage = ( value: null | undefined | string | Error | Warning): string => { const prefix = ‘Error: ‘; // If it’s falsy (null, undefined, empty string), return “Unknown” with the prefix if (!value) { return prefix + ‘Unknown’; } // If it’s a string, return the string with the prefix if (isString(value)) { return prefix + value; } // If it’s a Warning, return the Warning.text with the prefix if (‘text’ in value) { return prefix + value.text; } // If it’s an Error, return the Error.message with the prefix if (value instanceof Error) { return prefix + value.message; } // We will never reach here throw new Error(`Invalid value type`);};interface Warning { text: string;}
相同的代码,我们只是在一个函数中隔离了守卫,对吧? 不,它坏了。 TypeScript 没有将类型缩小为字符串,防护不起作用。
事情是这样的,isString() 返回一个布尔值,我们知道这个布尔值的含义。
const isString = (value: any): boolean => typeof value === ‘string’;
这意味着参数是一个字符串。 但是 TypeScript 不知道那个布尔值是什么意思,所以让我们教它。
与其说我们的函数返回一个布尔值,不如说我们的函数返回问题的答案:“这个参数是字符串吗?”。
鉴于我们的参数的名称是 value,我们使用以下语法来做到这一点:value 是字符串。
const isString = (value: any): value is string => typeof value === ‘string’;
现在 TypeScript 知道 isString() 是一个类型保护并且我们的 formatErrorMessage() 函数可以正确编译。
我们的 isString() 函数的返回类型不再只是一个布尔值,它是一个“类型谓词”。
因此,要制作自定义类型保护,您只需定义一个返回类型谓词的函数。
所有类型谓词都采用 { parameter } is { Type } 的形式。
未知类型
在我们继续之前的快速提示:
如果我们使用未知类型,我们的代码会更安全,而不是在我们的自定义保护参数中使用类型 any。
const isString = (value: unknown): value is string => typeof value === ‘string’;
我制作了一个一分钟的视频来解释任何和未知之间的区别,链接在参考资料中。
自定义警卫
让我们通过将 formatErrorMessage() 函数中的所有检查转换为自定义守卫来锻炼我们的知识。
我们已经有了字符串保护,现在我们需要警告、错误和虚假类型的保护。
错误防护
Error 的保护非常简单,我们只是将 instanceof 操作符检查隔离在一个函数中。
const isError = (value: unknown): value is Error => value instanceof Error;
警戒卫士
但另一方面,Warning 守卫并不是那么简单。
TypeScript 允许我们使用 in 运算符,因为我们的 value 参数可以是有限数量的类型,并且它们都是对象。
const formatErrorMessage = ( value: null | undefined | string | Error | Warning): string => { const prefix = ‘Error: ‘; // If it’s falsy (null, undefined, empty string), return “Unknown” with the prefix if (!value) { return prefix + ‘Unknown’; } // If it’s a string, return the string with the prefix if (isString(value)) { return prefix + value; } // If it’s a Warning, return the Warning.text with the prefix if (‘text’ in value) { return prefix + value.text; } // If it’s an Error, return the Error.message with the prefix if (isError(value)) { return prefix + value.message; } // We will never reach here throw new Error(`Invalid value type`);};interface Warning { text: string;}
但是如果我们创建一个函数并说我们的参数是未知的,那么它可以是任何东西。 包括原始类型,这会引发错误,因为我们只能在对象中使用 in 运算符。
interface Warning { text: string;}const isWarning = (value: unknown): value is Warning => ‘text’ in value; // Compilation error
解决方案是在使用 in 运算符之前确保我们的参数是一个有效的对象。 我们还需要确保它不为空。
interface Warning { text: string;}const isWarning = (value: unknown): value is Warning => typeof value === ‘object’ && value !== null && ‘text’ in value;
假守卫
对于虚假值守卫,我们首先需要定义一个类型,其值被认为是虚假的。
type Falsy = false | 0 | -0 | 0n | ” | null | undefined;
我在这里不包括 NaN,因为 TypeScript 中没有 NaN 类型。
type Falsy = false | 0 | -0 | 0n | ” | null | undefined | ~~NaN~~;
NaN 的类型是数字,并非所有数字都是假的,所以这就是我们不处理 NaN 的原因。
typeof NaN;//=> number
有一个提议将 NaN 添加为一种类型——以及整数、浮点数和无穷大。 我认为这很好,拥有这些类型会很有帮助。
// Proposaltype number = integer | float | NaN | Infinity;
我将在参考文献中留下该提案的链接。
无论如何,现在我们有了 Falsy 类型,我们可以创建一个 falsy 值守卫。
请记住,如果一个值在转换为布尔值时被认为是假的,那么它就是假的。 因此,要检查我们的值是否为假,我们可以使用抽象相等来查看它是否被转换为假。
type Falsy = false | 0 | -0 | 0n | ” | null | undefined;const isFalsy = (value: unknown): value is Falsy => value == false;
带有自定义警卫的 formatErrorMessage()
就是这样,我们现在拥有了 formatErrorMessage() 函数所需的所有自定义守卫。
// FUNCTIONconst formatErrorMessage = ( value: null | undefined | string | Error | Warning): string => { const prefix = ‘Error: ‘; // If it’s falsy (null, undefined, empty string), return “Unknown” with the prefix if (isFalsy(value)) { return prefix + ‘Unknown’; } // If it’s a string, return the string with the prefix if (isString(value)) { return prefix + value; } // If it’s a Warning, return the Warning.text with the prefix if (isWarning(value)) { return prefix + value.text; } // If it’s an Error, return the Error.message with the prefix if (isError(value)) { return prefix + value.message; } // We will never reach here throw new Error(`Invalid value type`);};// GUARDSconst isString = (value: unknown): value is string => typeof value === ‘string’;const isError = (value: unknown): value is Error => value instanceof Error;interface Warning { text: string;}const isWarning = (value: unknown): value is Warning => typeof value === ‘object’ && value !== null && ‘text’ in value;type Falsy = false | 0 | -0 | 0n | ” | null | undefined;const isFalsy = (value: unknown): value is Falsy => value == false;
奖励:通过排除缩小范围
在我们结束之前,我想向你展示一些东西。
虚假值的列表是有限的,对吗?
1. `false`2. `0` `-0` `0n` representations of zero3. ““ `””` `”` empty string4. `null`5. `undefined`6. `NaN` not a number
但另一方面,真实值是无限的。 所有不虚假的价值观都是真实的。
那么,如何为真实值创建类型保护呢?
诚实守卫
诀窍是排除虚假类型。
我们不是检查我们的值是否为真,而是检查它是否_不_假。
type Truthy = Exclude;const isTruthy = (value: T): value is Truthy => value == true;// Testconst x = ‘abc’ as null | string | 0;if (isTruthy(x)) { x.trim(); // `x: string`}
我经常使用这个技巧,我们将在以后的文章中再次看到它。
结论
参考资料和其他链接如下。
如果您还没有,请在社交媒体上点赞、订阅和关注我们。 这有助于我们成长,从而为您带来更多免费内容。 这是双赢的。