本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.
技巧42:对于未知类型的值用unknown而不是any
如果你想写解析YAML文件的函数。那么你的函数改返回什么类型? 你可以写成any:
function parseYAML(yaml: string): any {
// ...
}
但是在技巧38中提到过:any 类型具有传染性。理想情况,你可以对返回值声明类型:
interface Book {
name: string;
author: string;
}
const book: Book = parseYAML(`
name: Wuthering Heights
author: Emily Brontë
`);
如果没有显性类型声明,那么book默认的类型是any,那么将失去类型检查:
const book = parseYAML(`
name: Jane Eyre
author: Charlotte Brontë
`);
alert(book.title); // No error, alerts "undefined" at runtime
book('read'); // No error, throws "TypeError: book is not a
// function" at runtime
更安全的方法是将返回值标注为unknow类型:
function safeParseYAML(yaml: string): unknown {
return parseYAML(yaml);
}
const book = safeParseYAML(`
name: The Tenant of Wildfell Hall
author: Anne Brontë
`);
alert(book.title);
// ~~~~ Object is of type 'unknown'
book("read");
// ~~~~~~~~~~ Object is of type 'unknown'
想要理解unknown类型,从any的可分配角度来思考:
- 任何类型都可以分配给any
- any可以分配给任何类型
技巧7曾讨论过:将类型考虑成值的集合。但是这对于any不适用,因为any不可能既是所有集合的子集,又不可能所有集合是any的子集。
这是any的能力,也是any的问题。因为类型检查是根据集合原理,any的使用让类型检查失效。
unknow只满足上面的第一条性质,不满足第二条:
- 任何类型都可以分配给unknown never正好相反:
- never可以分配给任何类型
使用unknown的好处:当你获取unknown的属性的值时候就会报错,这会鼓励你添加何时类型类型:
const book = safeParseYAML(`
name: Villette
author: Charlotte Brontë
`) as Book;
alert(book.title);
// ~~~~~ Property 'title' does not exist on type 'Book'
book('read');
// ~~~~~~~~~ this expression is not callable
这样的报错更让人理解。由于unknown不能赋值给其他类型,所以这这里添加类型断言。
当你不知道值的类型时候,很适合用unknown。 还有另外一个例子 在GeoJSON规范中,properties属性是未知,适合用unknown:
interface Feature {
id?: string | number;
geometry: Geometry;
properties: unknown;
}
类型断言不是转换unknown类型的唯一方法。instanceof检查也可以:
function processValue(val: unknown) {
if (val instanceof Date) {
val // Type is Date
}
}
也可以使用用户自定义的类型守护:
function isBook(val: unknown): val is Book {
return (
typeof(val) === 'object' && val !== null &&
'name' in val && 'author' in val
);
}
function processValue(val: unknown) {
if (isBook(val)) {
val; // Type is Book
}
}
想要判断是不是Book类型要考虑全面,不仅判断val是不是object,也要判断是不是null(因为 typeof null === 'object')。
你也会看到一种泛型参数的方法替代unknown:
function safeParseYAML<T>(yaml: string): T {
return parseYAML(yaml);
}
但是这在ts中被认为是不好的代码风格。它和类型断言看起来不一样,但是功能是一样的。最好是返回unknown类型,强迫使用者用断言。
在双重类型断言上,unknown可以用代替any:
declare const foo: Foo;
let barAny = foo as any as Bar;
let barUnk = foo as unknown as Bar;
unknown会更安全。
最后,有的时候会看到有人使用{},object代替unknown。但是前面两者比unknown范围更窄:
{}代表所有值除了,null和undefined- object代表非基本类型:object,arrays,functions
当你确定类型不会是null,undefined,那你就可用
{}代替undefined