一起养成写作习惯!这是我参与「掘金日新计划 · 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断言.