TypeScript 类型使用总结

一、ts 类型

1. 基本类型:string, number, boolean

Note: The type names String, Number, and Boolean (starting with capital letters) are legal, but refer to some special built-in types that will very rarely appear in your code. Always use string, number, or boolean for types.

2.any

TypeScript also has a special type, any, that you can use whenever you don’t want a particular value to cause typechecking errors.

When a value is of type any, you can access any properties of it (which will in turn be of type any), call it like a function, assign it to (or from) a value of any type, or pretty much anything else that’s syntactically legal:

1
2
3
4
5
6
7
8
9
let obj: any = { x: 0 };
// None of the following lines of code will throw compiler errors.
// Using `any` disables all further type checking, and it is assumed
// you know the environment better than TypeScript.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

3. 对象类型

Apart from primitives, the most common sort of type you’ll encounter is an object type. This refers to any JavaScript value with properties, which is almost all of them! To define an object type, we simply list its properties and their types.

For example, here’s a function that takes a point-like object:

1
2
3
4
5
6
// The parameter's type annotation is an object type
function printCoord(pt: { x: number; y: number }) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });

,; 都可以作为对象属性声明的分隔符。每个对象属性的类型都是非必传的,如果不传则默认为 any。

还可以给对象属性添加 readonly,代表该属性值不可修改。

1
2
3
4
5
6
7
8
9
interface Device {
readonly id: string;
type: string;
}
let phone = {
id: '23fe-2343-3342',
type: 'android'
}
phone.id = 'test'; // error

如果作为函数入参的对象中存在非必传的属性,可以在其属性名后加上?

1
2
3
4
5
6
function printName(obj: { first: string; last?: string }) {
// ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });

在 js 中,当获取一个不存在的属性时,会得到 undefined。因此,在使用它的时候,需要先确定该值是否为 undefined

1
2
3
4
5
6
7
8
9
10
11
12
function printName(obj: { first: string; last?: string }) {
// Error - might crash if 'obj.last' wasn't provided!
console.log(obj.last.toUpperCase());
Object is possibly 'undefined'.
if (obj.last !== undefined) {
// OK
console.log(obj.last.toUpperCase());
}

// A safe alternative using modern JavaScript syntax:
console.log(obj.last?.toUpperCase());
}

TypeScript also has a special syntax for removing null and undefined from a type without doing any explicit checking. Writing ! after any expression is effectively a type assertion that the value isn’t null or undefined:

1
2
3
4
function liveDangerously(x?: number | null) {
// No error
console.log(x!.toFixed());
}

Just like other type assertions, this doesn’t change the runtime behavior of your code, so it’s important to only use ! when you know that the value can’t be null or undefined.

4. 并集类型(Union Types)

TypeScript will only allow you to do things with the union if that thing is valid for every member of the union. For example, if you have the union string | number, you can’t use methods that are only available on string:

1
2
3
4
function printId(id: number | string) {
console.log(id.toUpperCase());
// Property 'toUpperCase' does not exist on type 'string | number'.
}

The solution is to narrow the union with code, the same as you would in JavaScript without type annotations. Narrowing occurs when TypeScript can deduce a more specific type for a value based on the structure of the code.

1
2
3
4
5
6
7
8
9
function printId(id: number | string) {
if (typeof id === "string") {
// In this branch, id is of type 'string'
console.log(id.toUpperCase());
} else {
// Here, id is of type 'number'
console.log(id);
}
}

5. 类型别名(Type)

虽然能直接在类型注解中编写对象类型和联合类型,但如果想要复用同一个类型,可以通过一个名称引用它。

1
2
3
4
5
6
7
8
9
10
11
12
type Point = {
x: number;
y: number;
};

// 与前面的示例完全相同
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}

printCoord({ x: 100, y: 100 });

不只是对象类型,你可以使用类型别名为任何类型命名。例如,类型别名可以命名联合类型:

1
type ID = number | string;

6.接口(Interface)

接口声明 是命名对象类型的另一种方式:

1
2
3
4
5
6
7
8
9
10
11
interface Point {
x: number;
y: number;
}

function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}

printCoord({ x: 100, y: 100 });

类型别名和接口之间的区别
类型别名和接口非常相似,在大多数情况下可以在两者间自由选择。几乎所有的 interface 功能都可以在 type 中使用,关键区别在于类型不能重新开放以修改或添加新的属性,而接口始终是可扩展的。

1
2
3
4
5
6
7
8
扩展接口
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}

1
2
3
4
5
6
7
通过 "&" 扩展类型
type Animal = {
name: string;
}
type Bear = Animal & {
honey: boolean
}

7. 字面量类型(Literal Types)

In addition to the general types string and number, we can refer to specific strings and numbers in type positions. By combining literals into unions, you can express a much more useful concept - for example, functions that only accept a certain set of known values:

1
2
3
4
5
6
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
// Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.

8. 枚举(Enums)

Enums are a feature added to JavaScript by TypeScript which allows for describing a value which could be one of a set of possible named constants. Unlike most TypeScript features, this is not a type-level addition to JavaScript but something added to the language and runtime. Because of this, it’s a feature which you should know exists, but maybe hold off on using unless you are sure.
Using an enum is simple: just access any member as a property off of the enum itself, and declare types using the name of the enum:

1
2
3
4
5
6
7
8
9
10
enum UserResponse {
No = 0,
Yes = 1,
}

function respond(recipient: string, message: UserResponse): void {
// ...
}

respond("Princess Caroline", UserResponse.Yes);

If we wanted, we could leave off the initializers entirely:

1
2
3
4
5
6
enum Direction {
Up,
Down,
Left,
Right,
}

Here, Up would have the value 0, Down would have 1, etc. This auto-incrementing behavior is useful for cases where we might not care about the member values themselves, but do care that each value is distinct from other values in the same enum.

9. 数组类型(Array)

To specify the type of an array like [1, 2, 3], you can use the syntax number[]; this syntax works for any type (e.g. string[] is an array of strings, and so on). You may also see this written as Array, which means the same thing. We’ll learn more about the syntax T when we cover generics.

1
let arr: number[] = [1, 2, 3];

10. 元组类型(Tuple)

A tuple type is another sort of Array type that knows exactly how many elements it contains, and exactly which types it contains at specific positions.

9. 其他类型

null, undefined, bigInt, symbol, void

1
let arr:[number, string] = [1, 2, 3, 'hello world'];

二、类型注释(Type Annotations)

1. 对变量的 TA

1
2
// 对变量的类型注释
let name:string = 'jankin';

当使用 var、let、const 来声明一个变量时,可以选择性地在变量后添加类型注释来声明变量的类型。但在大部分使用场景中,这并不是必需的。因为 ts 会自动推断类型。例如,在上面的代码中,即使不加上类型注释,name 变量也会基于它的初始值被赋予 string 类型。

2. 对函数入参和返回值的 TA

1
2
3
function checkIfLargeThan20(data: number): boolean {
return data > 20
}

其实大部分情况下,不需要对函数的返回值进行类型声明。因为 ts 会根据函数 return 的语句自行推断类型。一些代码库会为了将其作为文档记录避免日后被篡改,又或因个人偏好,而显式地加上返回值的类型声明。

三、类型断言(Type Assertions)

Sometimes you will have information about the type of a value that TypeScript can’t know about.

For example, if you’re using document.getElementById, TypeScript only knows that this will return some kind of HTMLElement, but you might know that your page will always have an HTMLCanvasElement with a given ID.

In this situation, you can use a type assertion to specify a more specific type:

1
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

You can also use the angle-bracket syntax (except if the code is in a .tsx file), which is equivalent:

1
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

参考文档

  1. TypeScript 常见类型