[译]<<Effective TypeScript>> 技巧42:对于未知类型的值用unknown而不是any

619 阅读3分钟

本文的翻译于<<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