类型
基本类型
八种基础数据类型
let str: string = "jimmy";
let num: number = 24;
let bool: boolean = false;
let u: undefined = undefined;
let n: null = null;
let obj: object = { x: 1 };
let big: bigint = 100n;
let sym: symbol = Symbol("me");
important
默认情况下 undefined
和 null
是其他类型(除 never
和 void
)的子类型,也就是说你可以把 undefined
和 null
赋值给其他类型的变量。
void
常用于定义函数的返回值类型,当一个函数没有任何返回值,则可以将其返回值类型定义为 void
。
function foo(): void {
console.log("hello ts");
}
never
该类型用来表示永远不会存在的值的类型,常用于:
- 抛出异常的函数
- 死循环函数
function err(msg: string): never {
throw new Error(msg);
}
function loop(): never {
while (true) {}
}
never
是没有子类型的,即除了 never
类型本身的变量,其余类型变量无法赋值给 never
类型变量,利用这个特性我们可以来判断函数中的联合类型参数的所有可能取值情况是否讨论完毕。
function foo(n: string | number) {
if (typeof n === "string") {
} else if (typeof n === "number") {
} else {
let check: never = n;
}
}
注意第 5 行,如果函数参数类型我们判断完毕后,第 5 行中的 n 一定是 never
类型,因此可以成功赋值给同为 never
类型的 check 变量,这样可以编译通过,但是如果前面参数类型没有讨论所有的情况,那么第 5 行的 n 就不会是 never
,这会导致编译出错。
any
该类型可以表示任意类型的变量,因此任何类型的变量都可以赋值给 any
类型的变量,并且 any
类型的变量也可以赋值给其他任何类型,请慎用 any
类型,因为使用它就会导致 TypeScript 的类型保护机制失效,在需要使用 any
的地方先考虑能否用 unknow
。
important
正是因为 任何类型的变量都可以赋值给 any
类型的变量,并且 any
类型的变量也可以赋值给其他任何类型,这个过程中就算双方变量的取值类型并不匹配,也不会报错。
let a: any = 100;
let b: string = a; // 不会报错,b变量依旧是字符串类型,但是它的取值实际上是一个数值
console.log(b); // 100
unknown
该类型和 any
类型一样,任何类型的取值都可以赋值给 unknown
类型的变量,但是 any
类型不同的是,该类型变量取值不能赋值给其他类型。
let a: unknown = 100;
let b: string = a; // 报错
并且如果 unknown
类型的变量取值是一个函数,我们也无法对其调用,必须使用 typeof
或者类型断言等手段对其缩小类型范围。
let foo: unknown = () => {};
foo(); // 报错
enum
数字枚举
enum Direction {
Up,
Left,
Right,
Down
}
console.log(Direction.Up); // 0
console.log(Direction[0]); // "Up"
默认情况下 Direction.Up
的值为 0,后面的每以项都会 + 1。
我们也可以设置某一项的初始值,后续每一项会在前一项的基础上 + 1。
enum Direction {
Up,
Left = 3,
Right,
Down
}
console.log(Direction.Right); // 4
console.log(Direction[0]); // "Up"
源码如下:
"use strict";
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Left"] = 1] = "Left";
Direction[Direction["Right"] = 2] = "Right";
Direction[Direction["Down"] = 3] = "Down";
})(Direction || (Direction = {}));
枚举类型的本质就是一个对象,让一个属性的键和值相互映射,所以这里的 Direction
结构如下:
var Direction = {
0: "Up",
1: "Left",
2: "Right",
3: "Down",
"Up": 0,
"Left": 1,
"Right": 2,
"Down": 3,
};
字符串枚举
在 TypeScript 2.4 版本,允许我们使用字符串枚举。在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
enum Direction {
NORTH = "NORTH",
SOUTH = "SOUTH",
EAST = "EAST",
WEST = "WEST",
}
ES5 代码如下:
"use strict";
var Direction;
(function (Direction) {
Direction["NORTH"] = "NORTH";
Direction["SOUTH"] = "SOUTH";
Direction["EAST"] = "EAST";
Direction["WEST"] = "WEST";
})(Direction || (Direction = {}));
由源码可以知道 数字枚举 比 字符串枚举 多了反向映射。
异构枚举
异构枚举的成员值是数字和字符串的混合。
enum Enum {
A,
B,
C = "C",
D = "D",
E = 8,
F,
}
ES5 代码如下:
"use strict";
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
Enum[Enum["B"] = 1] = "B";
Enum["C"] = "C";
Enum["D"] = "D";
Enum[Enum["E"] = 8] = "E";
Enum[Enum["F"] = 9] = "F";
})(Enum || (Enum = {}));
Array
let nums: number[] = [1, 2, 3, 4]; // 数值数组
let arr: (number | string)[] = [1, "2", 3, "4"]; // 数组中的元素可以是 number 或者 string 类型
Tuple
与数组类型很相似,但是常用于定义固定个数的数组,并且数组中每个位置的元素类型都是定好的。
let tuple: [number, string] = [1, "2"];
使用 ?
可以声明元组内部的可选元素。
let point: [number, number?, number?];
point = [1];
point = [1, 2];
point = [1, 2, 3];
使用 ...
运算符可以接收不定项元组中的元素。
let restTuple: [number, ...string[]] = [1, "2", "3", "4", "5"];
使用 readonly
修饰,定义只读元组。
let readonlyTuple: readonly [number, number] = [1, 2];
如果对该元组进行修改就会报错。
字面量类型
该类型可以限制变量只能在某几个 字符串、数值、布尔值 中取值。
let type: "up" | "down" | true | 0;
type 变量只能取值为 "up"
或 "down"
或 true
或者 0
,否者就会报错。
通常该类型会搭配 类型别名 一起使用。
函数类型
类型声明语法
对普通函数的类型定义。
// 两个参数必须为 number 类型,并且返回值也必须为 number 类型
function foo(x: number, y: number): number {
return x + y;
}
函数表达式的类型定义。
let bar: (x: number, y: number) => number = function (x, y) {
return x + y;
};
箭头函数的类型定义。
const paz = (x: number, y: number): number => x + y;
用接口定义函数类型。
interface fun {
(x: number, y: number): number;
}
可选参数
// 参数 x 必须传入,参数 y 可以选择不传入
function foo(x: number, y?: number) {
return y ? x + y : x;
}
foo(1); // 1
foo(1, 2); // 3
默认参数值
如果调用函数时,可选参数没有被传入,那么可以使用指定的默认值,需要注意,对于可选参数无法使用默认参数值,因为某种意义上来讲给一个参数设置了默认参数,这个参数就成为了可选参数。
// 参数 y 如果没有传入则默认为 0
function foo(x: number, y: number = 0) {
return x + y;
}
foo(1); // 1
foo(1, 2); // 3
剩余参数
使用 ...
运算符,调用函数时,将后续多余传入的参数放到一个数组中。
function foo(num1: number, num2: number, ...nums: number[]) {
if (!nums) return num1 + num2;
return num1 + num2 + nums.reduce((pre, cur) => pre + cur, 0);
}
foo(1, 2); // 3
foo(1, 2, 3, 4); // 10
函数重载
声明多个同名函数,然后实现其中一个函数。ts 的函数重载和 Java 中的不太一样,感觉没多少用处。
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: unknown, b: unknown) {
if (typeof a === "string" || typeof b === "string") {
return a.toString() + b.toString();
}
return a + b;
}
详情看 A Simple Explanation of Function Overloading in TypeScript
类型运算
类型断言
它具有如下作用:
- 强制类型转换
- 非空断言
- 确定赋值断言
强制类型转换
使用 as
或者尖括号的语法进行强制类型转换。
// 尖括号 语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// as 语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
以上两种方式虽然没有任何区别,但是尖括号格式会与 react 中 JSX 产生语法冲突,因此我们更推荐使用 as 语法。
非空断言
使用 !
后缀表达式用于断言一个变量不可能为 null
或 undefined
类型。
比如有一个变量 fn
它的类型可能是一个 undefined
也可能是一个函数,此时如果我们直接调用它 fn()
,就会报错,因为 fn
可能是一个 undefined
,但是我们可以使用 !
运算符,断言它不可能是一个 undefined
,这样就可以正常调用了 fn!()
。
function foo(fn?: () => void) {
fn(); // 报错
fn!();
}
确定赋值断言
允许在 实例属性 和 变量 声明后面放置一个 !
号,从而告诉 TypeScript 该属性会被明确地赋值。
let x!: number;
initX();
console.log(x + 1);
类型守卫
in
关键字用于判断某个字符串是否是一个对象的属性,[property] in [object]
typeof
关键字用于返回一个变量的数据类型字符串instanceof
关键字用于判断一个对象的构造函数,[object] instanceof [constructor]
类型别名
使用 type
关键字,我们可以给任何类型定义一个别名,通常配合字面量类型使用。
type ButtonType = "primary" | "default" | "link" | "danger";
另外,如果给一个对象类型设置了类型别名,我们就使用类型别名来获取对象类型中某个属性的类型:
type Person = {
name: string,
age: number,
};
let personName: Person["name"] = "DwD";
联合类型
使用 |
运算符来拼接类型,表示变量取值可以为其中之一。
let direction: "up" | "left" | "down" | "right";
交叉类型
使用 &
运算符来拼接类型,用于接口继承、对象类型继承。
// 这里也可以是个 interface
type BaseProps = {
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
}
type ExtendProps = {
onClick?: () => void;
}
type Props = BaseProps & ExtendProps;
此时 Props
类型相当于:
type Props = {
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
onClick?: () => void;
}
请注意,如果这里的 ExtendProps
里面的属性和 BaseProps
产生冲突:
type BaseProps = {
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
}
type ExtendProps = {
onClick?: () => void;
style?: React.CSSProperties;
}
- 如果冲突属性的类型一致,那么没有影响,会正常进行合并
- 如果冲突属性的类型不一致,会导致运算结果的该属性类型产生变化(会按照一定的规则进行运算,但是我目前还不知道规则是啥)
type BaseProps = {
className?: string;
style?: string;
children?: React.ReactNode;
}
type ExtendProps = {
onClick?: () => void;
style?: {};
}
type Props = BaseProps & ExtendProps;
运算后的 Props
里面的 style
属性会发生变化,但是请注意上面整个过程都不会编译报错。而且,这里的 BaseProps
和 ExtendProps
可以任意替换为 interface,效果等价于 type
。