第一章:了解 TypeScript
第1条 理解 TypeScript 和 JavaScript 之间的关系
第2条 了解你在使用的 TypeScript 配置
打开 tsconfig.json
中的 noImplicitAny
和 strictNullChecks
配置,尽可能避免运行时出现 undefined is not an object 等错误。
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true
}
}
在初始化变量时,如果赋值为 null
和 undefined
,明确标出对应类型。
const x: number = null;
const y: number = undefined;
const x: number | null = null;
const y: number | undefined = undefined;
第3条 理解代码生成和类型两者互相独立
无法在运行时检查 TypeScript 类型
interface Square {
width: number;
}
interface Rectangle extends Square {
height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape instanceof Rectangle) {
// 'Rectangle' only refers to a type, but is being used as a value here.
return shape.width * shape.height;
// Property 'height' does not exist on type 'Shape'.
// Property 'height' does not exist on type 'Square'.
} else {
return shape.width * shape.width;
}
}
TypeScript 编译成 JavaScript 后,类型信息会被擦除。可以通过检查结构的写法代替:
function calculateArea(shape: Shape) {
if ('height' in shape) {
shape; // Type is Rectangle
return shape.width * shape.height;
} else {
shape; // Type is Square
return shape.width * shape.width;
}
}
或者引入标记:
interface Square {
kind: 'square';
width: number;
}
interface Rectangle {
kind: 'rectangle';
height: number;
width: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape.kind === 'rectangle') {
shape; // Type is Rectangle
return shape.width * shape.height;
} else {
shape; // Type is Square
return shape.width * shape.width;
}
}
还可以使用 class
代替 interface
,这样就可以使用 instanceof
进行判断:
class Square {
constructor(public width: number) { }
}
class Rectangle extends Square {
constructor(public width: number, public height: number) {
super(width);
}
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape instanceof Rectangle) {
shape; // Type is Rectangle
return shape.width * shape.height;
} else {
shape; // Type is Square
return shape.width * shape.width; // OK
}
}
运行时类型和声明的类型可能不一样
function setLightSwitch(value: boolean) {
switch (value) {
case true:
turnLightOn();
break;
case false:
turnLightOff();
break;
default:
console.log(`I'm afraid I can't do that.`);
}
}
function turnLightOn() {
// ...
}
function turnLightOff() {
// ...
}
interface LightApiResponse {
lightSwitchValue: boolean;
}
async function setLight() {
const response = await fetch('/light');
const result: LightApiResponse = await response.json();
setLightSwitch(result.lightSwitchValue);
}
无法保证数据请求的结果和 TS 声明的 LightApiResponse
结构完全一致,如果响应中的 lightSwitchValue
为字符串 "true"
、null
,或者直接缺少该字段,都会产生预期之外的错误。因此在输入外部不可控的数据源时,依然需要防御性编程。
无法基于 TypeScript 类型实现函数重载
TypeScript 虽然提供了类型,但无法实现 C++ 等语言中的函数重载(Function Overload):
function add(a: number, b: number) { return a + b; }
// Duplicate function implementation.
function add(a: string, b: string) { return a + b; }
// Duplicate function implementation.
TypeScript 的重载只针对类型标注,允许有多个函数声明,但只能有一个实现:
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any) {
return a + b;
}
const three = add(1, 2); // Type is number
const twelve = add('1', '2'); // Type is string
第4条 适应结构化类型
interface Vector2D {
x: number;
y: number;
}
function calculateLength(v: Vector2D) {
return Math.sqrt(v.x * v.x + v.y * v.y);
}
interface NamedVector {
name: string;
x: number;
y: number;
}
const v: NamedVector = { x: 3, y: 4, name: 'Zee' };
calculateLength(v); // OK, result is 5
TypeScript 的类型被称为结构化类型(Structural Typing),和 C++、Java 不同,即使 NamedVector
不是继承于 Vector2D
,依然可以正常调用 calculateLength
。
当然这种设计也有它存在的问题,对于如下代码:
interface Vector3D {
x: number;
y: number;
z: number;
}
function normalize(v: Vector3D) {
const length = calculateLength(v);
return {
x: v.x / length,
y: v.y / length,
z: v.z / length,
};
}
normalize({ x: 3, y: 4, z: 5 }); // result is { x: 0.6, y: 0.8, z: 1 }
调用 normalize
时不会发现向量的 z
属性没有被 calculateLength
处理。因为只要传入的参数符合 Vector3D
结构,都视为合法。像下面的代码,在 TypeScript 会报类型错误:
function calculateLengthL1(v: Vector3D) {
let length = 0;
for (const axis of Object.keys(v)) {
const coord = v[axis];
// Element implicitly has an 'any' type because expression of type 'string'
// can't be used to index type 'Vector3D'. No index signature with a
// parameter of type 'string' was found on type 'Vector3D'.
length += Math.abs(coord);
}
return length;
}
const vec3D = { x: 3, y: 4, z: 1, address: '123 Broadway' };
calculateLengthL1(vec3D); // OK, returns NaN
可以重写为:
function calculateLengthL1(v: Vector3D) {
return Math.abs(v.x) + Math.abs(v.y) + Math.abs(v.z);
}