TypeScript学习笔记
tips
-
传递数组或者对象作为函数参数时,可以用 Readonly<> 包裹原来的类型声明,譬如
Readonly<Array<number>>,TypeScript 编译器会通过拒绝编译来保护这个参数不被修改。如果的确需要一个可以被修改的数组,可以通过 spread 操作 [...array] 来复制这个数组-
1function sortNumbers(array: Readonly<Array<number>>) { 2 return [...array].sort((a, b) => a - b); 3}
-
-
使用 unkown 而不是 any 来标注类型尚未明确的变量。Any 告诉 TypeScript 编译器,不需要检查类型;而 unkown 则是把检查留到使用变量的时候。使用时可以通过 typeof 运算符获取到变量的实际类型,通过 as 添加上类型后就可以正常使用了
-
使用 Records 来代替 Objects,因为可以限定键的范围。如
1type AllowedKeys = "name" | "age"; 2 3// use a type here instead of interface 4type Person = Record<AllowedKeys, unknown>; 5 6const Human: Person = { 7 name: "Steve", 8 age: 42, 9};
tsconfig
隐式推断
ts 有隐式类型推断。开启后隐式 any 的推断会被检查并报错。
1// x被隐式推断成any,抛出错误
2function getNum(x) {
3 return x;
4}
5// problems: Parameter 'x' implicitly has an 'any' type.
1// 主动声明x是any避免错误
2function getNum(x: any) {
3 return x;
4}
1// 这种情况不会报错,因为隐式推断成undefined
2let y;
3console.log(y);
1// 这种情况也不会报错,因为赋值的时候类型推断成number
2let z;
3z = 123;
4console.log(z);
严格检查
默认情况 undefined 和 null 是其他类型的子类型,也就是其他类型变量可以被赋值 null 或 undefined。开启后会检查这种情况并报错。
1let num: number = undefined;
2// problems:Type 'undefined' is not assignable to type 'number'.
错误不输出
ts 默认即使编译报错也会生成 Js 文件,这一项设置 true,可以在报错的时候,不生成 js
基础
标记类型
- boolean,number,string,null,undefined
- void(一般用于函数空返回值,void 类型的变量可以赋值 undefined,不能赋值 null)
- any(允许被赋值给任意类型,any 类型不会被类型检查)
- unkonwn(允许被赋值给任意类型)
类型推论
定义时未指定类型且未赋值会被推断成 any,若赋值则会推断成赋值的类型
1let myFavoriteNumber = "seven"; //推断成string
联合类型
‘或’
1let myFavoriteNumber: string | number;
交叉类型
‘与’
1interface A {
2 a: string;
3}
4interface B {
5 b: string;
6}
7let test: A & B = {
8 a: "test a",
9 b: "test b",
10};
对象的类型
描述对象的形状(shape)
1interface Person {
2 name: string;
3 age: number;
4}
5let tom: Person = {
6 name: "Tom",
7 age: 25,
8};
定义的变量比接口少一些属性或多一些属性都是不允许的
可选属性
1interface Person {
2 name: string;
3 age?: number;
4}
5
6let tom: Person = {
7 name: "Tom",
8};
任意属性
1interface Person {
2 name: string;
3 age?: number;
4 [propName: string]: any;
5}
6
7let tom: Person = {
8 name: "Tom",
9 gender: "male",
10};
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
1interface Person {
2 name: string;
3 age?: number;
4 [propName: string]: string;
5}
6
7let tom: Person = {
8 name: "Tom",
9 age: 25,
10 gender: "male",
11};
12
13// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
14// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
15// Index signatures are incompatible.
16// Type 'string | number' is not assignable to type 'string'.
17// Type 'number' is not assignable to type 'string'.
18
19//上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了。
一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
1interface Person {
2 name: string;
3 age?: number;
4 [propName: string]: string | number | undefined; // 因为age不一定有,可能是undefined
5}
6
7let tom: Person = {
8 name: "Tom",
9 age: 25,
10 gender: "male",
11};
只读属性
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性
简单例子
1interface Person {
2 readonly id: number;
3 name: string;
4 age?: number;
5 [propName: string]: any;
6}
7
8let tom: Person = {
9 id: 89757,
10 name: "Tom",
11 gender: "male",
12};
13
14tom.id = 9527;
15
16// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
17// 上例中,使用 readonly 定义的属性 id 初始化后,又被赋值了,所以报错了。
注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候:
1interface Person {
2 readonly id: number;
3 name: string;
4 age?: number;
5 [propName: string]: any;
6}
7
8let tom: Person = {
9 name: "Tom",
10 gender: "male",
11};
12
13tom.id = 89757;
14
15// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.
16// Property 'id' is missing in type '{ name: string; gender: string; }'.
17// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
18//例中,报错信息有两处,第一处是在对 tom 进行赋值的时候,没有给 id 赋值。第二处是在给 tom.id 赋值的时候,由于它是只读属性,所以报错了。
数组的类型
类型+方括号表示法
1let fibonacci: number[] = [1, 2, 3, 4];
泛型表示法(常用)
1let fibonacci: Array<number> = [1, 2, 3, 4];
接口表示法(不常见)
1interface NumberArray {
2 [index: number]: number;
3}
4let fibonacci: NumberArray = [1, 2, 3, 4];
类数组的表示
类数组不是数组,不能用数组表示,但是可以参照接口表示法来写
比如函数参数 arguments。但事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等
1function sum() {
2 let args: {
3 [index: number]: number;
4 length: number;
5 callee: Function;
6 } = arguments;
7}
8//上述等价于
9function sum() {
10 let args: IArguments = arguments;
11}
12//IArguments 是 TypeScript 中定义好了的类型,它实际上就是:
13interface IArguments {
14 [index: number]: any;
15 length: number;
16 callee: Function;
17}
函数的类型
函数声明式(简单,推荐)
1function sum(x: number, y: number): number {
2 return x + y;
3}
函数表达式
1let mySum: (x: number, y: number) => number = (
2 x: number,
3 y: number
4): number => {
5 return x + y;
6};
7// 第一个冒号到第一个等号之间的可以省略,相当于不写type
8//ts会类型推断的,不然这么写
9//就像写了两遍
10
11//或者这么写,注意是Function不是function,其实也相当于少写了type
12let newSum: Function = (x: number, y: number): number => {
13 return x + y;
14};
用接口定义函数,注意参数的括号
1interface SearchFunc {
2 (source: string, subString: string): boolean;
3}
4let mySearch: SearchFunc = (source: string, subString: string): boolean => {
5 return source.search(subString) !== -1;
6};
可选参数(可选参数要放在最后),参数默认值
1function buildName(firstName?: string, lastName: string = "Clark"): string {
2 if (firstName) {
3 return firstName + lastName;
4 }
5 return lastName;
6}
剩余参数,...rest 访问剩余参数
1function myPush(array: Array<unknown>, ...rest: Array<unknown>) {
2 rest.forEach((item) => {
3 array.push(item);
4 });
5}
6myPush([], 1, 2, 3);
重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理
比如,我们需要实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'
1function reverse(x: number | string): number | string | void {
2 if (typeof x === "number") {
3 return Number(x.toString().split("").reverse().join(""));
4 } else if (typeof x === "string") {
5 return x.split("").reverse().join("");
6 }
7}
然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串
这时,我们可以使用重载定义多个 reverse 的函数类型:
1function reverse(x: number): number;
2function reverse(x: string): string;
3function reverse(x: number | string): number | string | void {
4 if (typeof x === "number") {
5 return Number(x.toString().split("").reverse().join(""));
6 } else if (typeof x === "string") {
7 return x.split("").reverse().join("");
8 }
9}
10//上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
11
12//注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
类型断言
语法
- 值 as 类型(推荐)
<类型>值 (不推荐,tsx 语法中<Foo>表示 ReactNode;而在 ts 中泛型也是这种语法,容易混淆)- 作用:给一个类型断言,让 ts 编译器听你的,若使用不当会造成运行时错误。常用于 ts 的一些不好解决的报错
- 示例
1interface Cat {
2 name: string;
3 run(): void;
4}
5interface Fihs {
6 name: string;
7 swim(): void;
8}
9function isFish(animal: Cat | Fish) {
10 if (typeof (animal as Fish).swim) {
11 return true;
12 }
13 return false;
14}
1(window as any).foo = 1;
1function getCacheData(key: string): any {
2 return (window as any).cache[key];
3}
4
5interface Cat {
6 name: string;
7 run(): void;
8}
9
10const tom = getCacheData("tom") as Cat;
11tom.run();
1//用泛型可以更好地实现上述代码
2function getCacheData<T>(key: string): T {
3 return (window as any).cache[key];
4}
5
6interface Cat {
7 name: string;
8 run(): void;
9}
10
11const tom = getCacheData<Cat>("tom");
12tom.run();

评论列表:
暂无评论 😭