TypeScript 中的 unknown 类型

早在 TypeScript 3.0 中就新增加了一种 unkown 类型 New unknown top type,然而目前中文文档似乎还没有相关的说明,而英文文档中也仅有简短的一点说明。那么 unknown 的应用场景是什么,为什么需要新增这个类型呢?以下为结合官方 wiki,就比较常见的应用场景做的个人笔记总结。

如果将类型看作一个集合的话,那么顶部类型则包含了所有其他类型,而底部类型则不包含其他所有类型。在 typescript 中,底部类型则是 never。而 unknown 类型与 any 非常相似,都是顶部类型。

我们知道如果使用了 any,那就几乎意味着放弃了类型保护,我们可以使用 any 做任何事情,而 unknown 却恰恰相反,我们几乎无法利用 unknown 来做任何事情。

function f1(a: any) {
  a.b(); // OK
}
function f2(a: unknown) {
  a.b();
  // error: Object is of type 'unknown'.
}

要使用 unknown 前须通过类型断言等方式确定类型,因此在面对未知类型时,使用 unknown 比 any 更加安全。

declare interface A {
  b: Function;
}

declare function isA(x: unknown): x is A;

function func(x: unknown) {
  // 类型断言
  (x as number).toFixed(2);
  
  // 相等
  if(x === 123) {
    x; // 123
  }
  
  // 类型防护
  if (typeof x === 'string' || typeof x === 'number') {
    x; // string | number
  }
  if (x instanceof Error) {
    x; // Error
  }
  
  // 断言函数
  if (isA(x)) {
    x.b();
  }
}

那么 unknown 与 any 具体还有哪些区别呢?

  1. 作为顶部类型,则意味着所有的类型都可以赋值给 unknown,然而 unknown 只能赋值给 any 或者 unknown。

    // Anything is assignable to unknown
    function f21<T>(pAny: any, pNever: never, pT: T) {
      let x: unknown;
      x = 123;
      x = "hello";
      x = [1, 2, 3];
      x = new Error();
      x = x;
      x = pAny;
      x = pNever;
      x = pT;
    }
    // unknown assignable only to itself and any
    function f22(x: unknown) {
      let v1: any = x;
      let v2: unknown = x;
      let v3: object = x; // Error
      let v4: string = x; // Error
      let v5: string[] = x; // Error
      let v6: {} = x; // Error
      let v7: {} | null | undefined = x; // Error
    }
  2. 在联合类型中,any 交叉(&)或者联合(|)任何类型, 除了 never 外都为 any,而 unknown 交叉(&)任何类型都为其所交叉类型,联合(|)任何类型(除了 any 外)都为 unknown

    type T00 = unknown & null; // null
    type T03 = unknown & string; // string
    type T06 = unknown & any; // any
    
    type T00 = any & null; // any
    type T02 = any & null & undefined; // null & undefined (which becomes never)
    type T03 = any & string; // any
    type T10 = unknown | null; // unknown
    type T13 = unknown | string; // unknown
    type T16 = unknown | any; // any
    
    type T10 = any | null; // any
    type T12 = any | null | undefined; // any
    type T13 = any | string; // any
  3. 在之前泛型类型默认为 {}, 现在则默认为 unknown。

    function foo<T>(x: T): [T, string] {
        return [x, x.toString()]
        //           ~~~~~~~~ TypeScript 3.5 之后将会报错:error! Property 'toString' does not exist on type 'T'.
    }

    如果要不报错,可以这么写

    function foo<T extends {}>(x: T): [T, string] {
        return [x, x.toString()]
    }
  4. function 如果返回的是 unknown ,则可以没有返回语句

    function f27(): unknown {}

简单来说,unknown 主要的场景就是,当我们需要一个值,这个值可以是任何类型,但是在使用这个值前,需要先判断类型以确保类型安全。换句话说,在使用 any 前,可以先试试 unknown。