TypeScript 2.9+ 版本中的几个知识点

1,157 阅读4分钟

resolveJsonModule

TypeScript 2.9 版本中添加了一个 resolveJsonModule 编译选项,它允许我们在 TypeScript 模块中导入 JSON 文件。

在 NodeJS 中,我们通常会导入一些 .json 文件,如下所示:

// config.json
{
    "debug": true
}
const config = require('./config.json');

config.debug === true // true

当重写为 TypeScript 之后,仅仅是将 require 语法改写成 ES Module,而不做其他修改,TypeScript 将会抛出错误:

import config from './1.json'; // Error: Cannot find module './1.json'

这是由于在默认情况下,相对路径导入模块时,TypeScript 只识别 .ts/tsx 文件模块。如果你使用的是 TypeScript 2.9 前的版本,你可能会用以下方式:

declare module '*.json';

但是它也只 decalre 了一个模块,模块内容还是 any,也就是无法得到一些代码提示(无法得到有关键值对信息)。

TypeScript 2.9 添加 resolveJsonModule 编译选项,很好的解决了这个问题:

config.png

unknown 替代 any

使用 TypeScript 的过程中,难免会有使用 any 的情况,这个时候倒不如换个方式,使用 unknown 试试。

unknown 最早出现在此 PR 中,随 3.0 一起发布。它被认为是安全版的 any,与 any 不同的是,unknown 仅能赋值给 any、unknown 类型,以及 unknown 上不存在任何属性与方法。

let a: any = 10;        // 任何类型都能赋值给 any
let u: unknown =  10;   // 与 any 一样,任何类型都能赋值给 unknown


let s1: string = a;     // any 能赋值给任何类型
let s2: string = u;     // 不能把 unknown 赋值给除 any、unknow 以外的其他类型

a.method();             // any 上有任意的属性和方法
u.method();             // unknown 没有被断言到一个确切类型之前,不具备任何属性和方法

当然,unknown 也能被断言,或是类型缩小至一个指定的范围:

const name: unknown = 'hello';
const str = name as string;       // unknown 断言为 string 类型
const oStr = str.toUpperCase();   // HELLO

function doSome(x: unknown) {
  if (typeof x === 'string') {
    // 在这个块中,TypeScript 知道 `x` 的类型必须是 `string`
    console.log(x.subtr(1));      // Error: 'subtr' 方法并没有存在于 `string` 上
    console.log(x.substr(1));     // ok
  }

  if (x instanceof Date) {
    // x 是 Datee 类型
    console.log(x.toISOString()); // ok
  }
}

也可以使用自定义保护类型:

function isNumberArray(value: unknown): value is number[] {
  return (
    Array.isArray(value) &&
    value.every(element => typeof element === "number")
  );
}

const unknownValue: unknown = [15, 23, 8, 4, 42, 16];

if (isNumberArray(unknownValue)) {
  // unknownValue 类型是 number[]
  const max = Math.max(...unknownValue);
  console.log(max);
}

对于 unknown 的使用,官方推荐的用法是:

我们经常需要在 TypeScript 中描述功能最少的类型。这对于那些「希望是任何类型,但是在使用之前必须执行某种类型检查」非常有用,它强制使用者安全性的思考它返回的值。

此外,在即将发布的 3.5 版本中,泛型参数的隐式类型由 {} 类型,变成 unknown,即,在 3.5 以下版本时,可以:

function test<T>(params: T) {
    return params.toString(); // ok
}

3.5 版本以上,这将会报错:

function test<T>(params: T) {
    return params.toString(); // ERROR: Property 'toString' does not exist on type 'T'.
}

你可以这么做来修复它:

function test<T extends {}>(params: T) {
    return params.toString(); // ok
}

const 断言

当声明一个可变变量或者属性时,TypeScript 通常会扩展变量类型,来确保我们在不编写显示类型时,可以赋值内容:

let x = 'hello';  // x 的类型是 string

// 可以重新赋值
x = 'world';

你也可以声明一个字面量类型,在接下来将不能被重新赋值:

let x: 'hello' = 'hello';  // x 的类型是 hello
// 或者是 x = 'hello' as 'hello'

// error,不能重新赋值
x = 'world';

这种方式似乎很极端,但在某些情况下,却是很有用:

interface Test {
  pos: 'left' | 'right' | 'top' | 'bottom'
}

function func() {

  // 可能是经过一系列计算得到的值
  return {
    pos: 'left'
  }
}

  // Error: TypeScript 将会把 func 推断为 { pos: string },而不是 { pos: 'left' }
let t: Test = func();

你可以通过字面量解决这个问题:


function func() {

  // 可能是经过一系列计算得到的值
  return {
    pos: 'left' as 'left'
  }
}

  // Error: TypeScript 将会把 func 推断为 { pos: string },而不是 { pos: 'left' }
let t: Test = func();

随着数据结构逐渐变得复杂,这种方式变得越来越麻烦,const 断言的出现,正是为了解决这样的问题。

function func() {

  // 可能是经过一系列计算得到的值
  return {
    pos: 'left'
  } as const
}

// ok
let t: Test = func();

使用 const 断言时:

  • 表达式中的字面量类型不应该被扩展(如,不应该从字面量类型 hellostring 类型)
  • 对象字面量类型属性只读
  • 数组字面量成为 readonly tuples

即:

let obj = {
  x: 10,
  y: ['hello', 2'],
  z: {
    a:
      {  b: 'top' }
  }
} as const;

// obj 的类型是:
// {
//   readonly x: 10;
//   readonly y: readonly ["hello", "2"];
//   z: {
//     a: {
//       readonly b: "top";
//     };
//   };
// }

参考

更多文章,请关注公众号:

微信服务号