[译]<<Effective TypeScript>> 高效TypeScript62个技巧 技巧21

158 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情

本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.

技巧21: 理解类型扩展

ts中存在类型推断机制:

let p = 'h'; // type is string

ts根据一个 'h' 推断出p 是string, 这就是类型扩展.理解类型扩展能帮你理解相关错误, 还能让你更高效使用类型申明.

假如你正在写一个库处理 vector向量, 你写个一个 type 给 3D vector , 和一个函数的得到它任意的值:

interface Vector3 { x: number; y: number; z: number; }
function getComponent(vector: Vector3, axis: 'x' | 'y' | 'z') {
  return vector[axis];
}

但是当你使用的时候, 会报错:

let x = 'x';
let vec = {x: 10, y: 20, z: 30};
getComponent(vec, x);
               // ~ Argument of type 'string' is not assignable to
               //   parameter of type '"x" | "y" | "z"'

报错的原因: x 的 type 被推断为 string, 但是getComponent函数的第二个参数期待一个更明确的类型. 所以导致了一个错误.

这个过程是不明确的, 因为给定一个值会有多种的可能,比如:

const mixed = ['x', 1];

mixed 是什么类型? 有很多种可能:

  • ('x' | 1)[]
  • ['x', 1]
  • [string, number]
  • readonly [string, number]
  • (string|number)[]
  • readonly (string|number)[]
  • [any, any]
  • any[]

在没有更多的上下文的情况下, ts也不知道那个是对的.这里ts推断出:(string|number)[]

在开头的那个例子中, x的类型被推断为string, 是因为ts允许这样的代码存在:

let x = 'x';
x = 'a';
x = 'Four score and seven years ago...';

将x推断为string, 是因为ts尝试在明确性和灵活性找到平衡点. 有个普遍性的原则: 变量在声明后, 它的类型不能改变. 所以string 比: string | RegExp 或者说 string | string[] 或者 any 更合理.

ts也有几种方法去处理:用const: 会得到一个更小的类型:

const x = 'x';  // type is "x"
let vec = {x: 10, y: 20, z: 30};
getComponent(vec, x);  // OK

因为x不能被指定, ts才能推测一个更小的type.

但是const也不是万能的, 比如 array, object. 相同的问题出现在object上. 下面的代码在js中是可行的:

const v = {
  x: 1,
};
v.x = 3;
v.x = '3';
v.y = 4;
v.name = 'Pythagoras';

变量v的类型可能是 {readonly x: 1}, 也可能是:{x: number}.更宽的类型: {[key: string]: number}, object.

ts对待object里面每个属性的逻辑: 假定每个属性都被指定为let,所有这里v的type为: {x: number}, 所以会报如下的错误:

const v = {
   x: 1,
 };
 v.x = 3;  // OK
 v.x = '3';
// ~ Type '"3"' is not assignable to type 'number'
 v.y = 4;
// ~ Property 'y' does not exist on type '{ x: number; }'
 v.name = 'Pythagoras';
// ~~~~ Property 'name' does not exist on type '{ x: number; }'

如果你知道更多上下文, 最好直接给个显示类型注释:

const v: {x: 1|3|5} = {
  x: 1,
};  // Type is { x: 1 | 3 | 5; }

还有一个方法,给 类型检查器传更多的上下文,比如将该值传给一个函数.更多的见 技巧26

第三个方法: 用 const 断言:

const v1 = {
  x: 1,
  y: 2,
};  // Type is { x: number; y: number; }

const v2 = {
  x: 1 as const,
  y: 2,
};  // Type is { x: 1; y: number; }

const v3 = {
  x: 1,
  y: 2,
} as const;  // Type is { readonly x: 1; readonly y: 2; }

当你写明了const断言, ts会赋予变量最小的类型, 同样的: 可以用const断言, 将array推断为一个tuple:

const a1 = [1, 2, 3];  // Type is number[]
const a2 = [1, 2, 3] as const;  // Type is readonly [1, 2, 3]

总之, 如果因为ts的推断类型范围太宽导致报错, 可以用显示类型申明或者const断言.