初识TypeScript
注意:本文章为TypeScript4 官方文档翻译阅读笔记
入门
ts
的好处- 静态检查,在运行之前就会报错
- 捕捉
js
中非异常的错误:比如读取对象不存在的属性不会报错,而是undefined
- 提示可能操作的属性
ts
写法并不会影响js
的运行结果- 编译后的
ts
文件并不会包含任何类型相关的代码,因为浏览器不识别ts
tsc
默认将ts
代码转换为ES3
,目前大多数浏览器已经支持了ES6
可以执行tsc --target es2015 hello.ts
设置编译后的目标版本。noImplicitAny
当类型被隐式推断为any
时,会抛出一个错误。strictNullChecks
决定null
和undefined
的行为。打开的情况下在操作之前需要先判断下是否存在(类型收窄),没打开的情况下可以被正确的访问,或者被赋值给任意类型的属性(可能会产生bug
)
基础的类型
-
原始类型:
string
、number
、boolean
-
数组:
number[]
或者Array<number>
-
any
简而言之就是不做类型检查 -
使用
let
、const
、var
声明变量的时候不是必须进行类型注解,ts
会根据初始值做出正确的类型推断 -
函数类型注解:
function functionName(params:paramsType):returnType{}
,没必要明确returnType
,因为ts
会根据return
语句推断正确的类型 -
对象类型:只需要简单的列出它的属性和对应的类型。
- 可选属性:
{prop?:propType}
,操作可选属性前判断属性是否存在:obj.prop?.doSomething
- 可选属性:
-
联合类型:基于已存在的类型构建新的类型。
params:string | number
表示params
可以是string
和number
其中任何一种类型- 虽然
params
的值是满足string
和number
其中任何一种类型即可,但是对于params
的操作,必须是string
和number
共有的,比如不能调用params.toUpperCase()
因为number
上不存在toUpperCase
方法
- 虽然
-
类型别名:为联合类型或者对象类型创建一个唯一的名称:
type typeName = string|number
-
接口:命名对象类型的另一种方式:
interface Animal {prpName:type}
-
类型别名和接口的不同:
- 类型别名无法添加新特性(声明同名类型会报错),接口可以添加新特性(直接声明同名接口,内容会被合并)
- 接口只用来声明对象的形状,不能重命名原始类型,类型别名可以
-
类型断言:当开发者比
ts
更加了解类型时,可以使用类型断言- 因为类型断言会在编译的时候被移除,所以运行时并不会有类型断言的检查,即使类型断言是错误的,也不会有异常或者 null 产生。
- 使用
as
进行类型断言:const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
- 使用尖括号进行类型断言:
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
但是在jsx
中不能用 - 仅仅允许类型断言转换为一个更加具体或者更不具体的类型。这个规则可以阻止一些不可能的类型指定:
const x = "hello" as number;
则不行。一定要这样的话可以使用双重断言:const a = (expr as any) as T;
-
字面量类型:当函数只能传入某些固定值时:
function printText(s: string, alignment: "left" | "right" | "center") {}
-
字面量推断:通常
ts
会将声明的变量设置为更加宽泛的类型,而不会推断为字面量类型:declare function handleRequest(url: string, method: "GET" | "POST"): void; const req = { url: "https://example.com", method: "GET" }; handleRequest(req.url, req.method); // Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
req.method
被推断为string
,而不是"GET"
,因为在创建req
和 调用handleRequest
函数之间,可能还有其他的代码,或许会将req.method
赋值一个新字符串比如"Guess"
。所以TypeScript
就报错了。有两种方式可以解决:
-
添加一个类型断言改变推断结果:
// Change 1: const req = { url: "https://example.com", method: "GET" as "GET" }; // Change 2 handleRequest(req.url, req.method as "GET");
修改 1 表示“我有意让
req.method
的类型为字面量类型"GET"
,这会阻止未来可能赋值为"GUESS"
等字段”。 修改 2 表示“我知道req.method
的值是"GET"”
. -
使用 as const 把整个对象转为一个类型字面量:
const req = { url: "https://example.com", method: "GET" } as const; handleRequest(req.url, req.method);
as const
效果跟const
类似,但是对类型系统而言,它可以确保所有的属性都被赋予一个字面量类型,而不是一个更通用的类型比如string
或者number
。
-
-
-
非空断言操作符:在任意表达式后面写上
!
,是一个有效的类型断言,表示它的值不可能是null
或者undefined
:console.log(x!.toFixed())
。注意只有在开发者确定表达式不为null
和undefined
的时候使用 -
枚举:会添加到语言运行时,一般不用,必须使用时使用。
类型收窄
-
类型收窄会将类型推倒为更精确的类型:
function padLeft(padding: number | string, input: string) { return new Array(padding + 1).join(" ") + input; // Operator '+' cannot be applied to types 'string | number' and 'number'. // 这里提示我们,把'string | number' 和 'number' 可能并不会达到我们想要的效果 }
function padLeft(padding: number | string, input: string) { if (typeof padding === "number") { return new Array(padding + 1).join(" ") + input; //在这里padding的类型会被推倒为number } return padding + input; // 这里padding的类型会被推倒为string } /** * TypeScript 要学着分析这些使用了静态类型的值在运行时的具体类型。目前 TypeScript 已经实现了比如 if/else 、三元运算符、循环、真值检查等情况下的类型分析。 在我们的 if 语句中,TypeScript 会认为 typeof padding === number 是一种特殊形式的代码,我们称之为类型保护 (type guard),TypeScript 会沿着执行时可能的路径,分析值在给定的位置上最具体的类型。 TypeScript 的类型检查器会考虑到这些类型保护和赋值语句,而这个将类型推导为更精确类型的过程,我们称之为收窄 (narrowing)。 */
typeof
类型保护:ts
会根据typeof
的结果进行类型收窄,针对object
结果,会将null
也包含在内- 真值收窄:根据
if
中的&&
、||
、!
进行类型收窄 - 等值收窄:使用
switch
语句和等值检查比如==
!==
==
!=
去收窄类型。 in
操作符收窄:in
用于判断对象中是否存在某个属性,通过in
的结果进行类型收窄instanceof
收窄:通过instanceof
结果进行类型收窄- 赋值语句收窄:根据赋值语句的右值,正确的收窄左值
-
类型判断式:通过一个函数实现类型保护,这个函数返回的类型是类型判断式
type Fish = { swim: () => void }; type Bird = { fly: () => void }; function isFish(pet: Fish | Bird): pet is Fish { // pet is Fish就是我们的类型判断式,一个类型判断式采用 parameterName is Type的形式,但 parameterName 必须是当前函数的参数名。 return (pet as Fish).swim !== undefined; } // Both calls to 'swim' and 'fly' are now okay. let pet = getSmallPet(); if (isFish(pet)) { pet.swim(); // let pet: Fish } else { pet.fly(); // let pet: Bird } // 也可以用 isFish 在 Fish | Bird 的数组中,筛选获取只有 Fish 类型的数组 const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()]; const underWater1: Fish[] = zoo.filter(isFish); // or, equivalently const underWater2: Fish[] = zoo.filter(isFish) as Fish[]; // 在更复杂的例子中,判断式可能需要重复写 const underWater3: Fish[] = zoo.filter((pet): pet is Fish => { if (pet.name === "sharkey") return false; return isFish(pet); });
-
可辨别联合:当联合类型中的每个类型,都包含了一个共同的字面量类型的属性,
TypeScript
就会认为这是一个可辨别联合,然后可以将具体成员的类型进行收窄。 -
穷尽检查:借助
never
类型可以赋值给任何类型,然而,没有类型可以赋值给never
(除了never
自身)。interface Triangle { kind: "triangle"; sideLength: number; } interface Circle { kind: "circle"; radius: number; } interface Square { kind: "square"; sideLength: number; } type Shape = Circle | Square | Triangle; function getArea(shape: Shape) { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; case "square": return shape.sideLength ** 2; default: const _exhaustiveCheck: never = shape; // Type 'Triangle' is not assignable to type 'never'. return _exhaustiveCheck; } } //因为 TypeScript 的收窄特性,执行到 default 的时候,类型被收窄为 Triangle,但因为任何类型都不能赋值给 never 类型,这就会产生一个编译错误。通过这种方式,你就可以确保 getArea 函数总是穷尽了所有 shape 的可能性。
函数
-
函数表达式:
type GreetFunction = (a: string) => void;
-
调用签名:用于函数有自己的属性值的情况
type DescribableFunction = { description: string; (someArg: number): boolean; }; function doSomething(fn: DescribableFunction) { console.log(fn.description + " returned " + fn(6)); }
-
构造签名:用于
new
操作符调用的函数type SomeConstructor = { new (s: string): SomeObject; }; function fn(ctor: SomeConstructor) { return new ctor("hello"); }
-
泛型函数:函数输出的类型取决于输入的类型,或者两个输入的类型以某种形式相互关联。泛型就是用一个相同类型来关联两个或者更多的值
//通过给函数添加一个类型参数 Type,并且在两个地方使用它,我们就在函数的输入(即数组)和函数的输出(即返回值)之间创建了一个关联 function firstElement<Type>(arr: Type[]): Type | undefined { return arr[0]; }
-
推断:不必明确指出泛型的类型,根据输入可以判断出来:
function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] { return arr.map(func); } // Parameter 'n' is of type 'string' // 'parsed' is of type 'number[]' const parsed = map(["1", "2", "3"], (n) => parseInt(n));
-
约束:对类型的参数进行限制
function longest<Type extends { length: number }>(a: Type, b: Type) { if (a.length >= b.length) { return a; } else { return b; } } // longerArray is of type 'number[]' const longerArray = longest([1, 2], [1, 2, 3]); // longerString is of type 'alice' | 'bob' const longerString = longest("alice", "bob"); // Error! Numbers don't have a 'length' property const notOK = longest(10, 100); // Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
正是因为我们对
Type
做了{ length: number }
限制,我们才可以被允许获取a
b
参数的.length
属性。没有这个类型约束,我们甚至不能获取这些属性,因为这些值也许是其他类型,并没有length
属性。基于传入的参数,
longerArray
和longerString
中的类型都被推断出来了。- 但是要注意:
function minimumLength<Type extends { length: number }>( obj: Type, minimum: number ): Type { if (obj.length >= minimum) { return obj; } else { return { length: minimum }; // Type '{ length: number; }' is not assignable to type 'Type'. // '{ length: number; }' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'. } } //其中的问题就在于函数理应返回与传入参数相同类型的对象,而不仅仅是符合约束的对象 // 'arr' gets value { length: 6 } const arr = minimumLength([1, 2, 3], 6); // and crashes here because arrays have // a 'slice' method, but not the returned object! console.log(arr.slice(0));
- 但是要注意:
-
声明类型参数:通常能自动推断泛型调用中传入的类型参数,也可以手动指定
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] { return arr1.concat(arr2); } const arr = combine<string | number>([1, 2, 3], ["hello"]);
-
函数的可选参数:使用
?
声明 -
回调函数中的可选参数:不能使用
?
声明,ts
会按照不会传入进行推断。正确的做法是不要在回调函数中设置可选参数,除非确定不传入这个参数。对于多余的参数,ts
会像js
一样忽略 -
函数重载:针对一个函数存在不同的调用方式的情况
举个例子。你可以写一个函数,返回一个日期类型
Date
,这个函数接收一个时间戳(一个参数)或者一个 月/日/年 的格式 (三个参数)。function makeDate(timestamp: number): Date; function makeDate(m: number, d: number, y: number): Date; function makeDate(mOrTimestamp: number, d?: number, y?: number): Date { if (d !== undefined && y !== undefined) { return new Date(y, mOrTimestamp, d); } else { return new Date(mOrTimestamp); } } const d1 = makeDate(12345678); const d2 = makeDate(5, 5, 5); const d3 = makeDate(1, 3); // No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
在这个例子中,我们写了两个函数重载,一个接受一个参数,另外一个接受三个参数。前面两个函数签名被称为重载签名 。
然后,我们写了一个兼容签名的函数实现,我们称之为实现签名 ,但这个签名不能被直接调用。尽管我们在函数声明中,在一个必须参数后,声明了两个可选参数,它依然不能被传入两个参数进行调用。 but 实现签名不能直接被调用这里不是很理解~~~
-
其他的一些类型
void
表示函数没有任何返回值,不等同于undefined
object
表示任何不是原始类型的值,函数也是对象,也可以有属性unknown
类似于any
但是更安全,对于unknown
做任何事情都是不合法的never
表示一个值不会再被观察到,比如函数中抛出异常
-
剩余参数:
function multiply(n: number, ...m: number[]) {}
-
参数结构:
function sum({ a, b, c }: { a: number; b: number; c: number }) {}
-
函数的可赋值性:除了函数字面量以外,即使规定了返回值类型为
void
,也不强制函数不能返回值:type voidFunc = () => void; const f1: voidFunc = () => { return true; }; const f2: voidFunc = () => true; const f3: voidFunc = function () { return true; };
当一个函数字面量定义返回一个 void 类型,函数是一定不能返回任何东西的:
function f2(): void { // @ts-expect-error return true; } const f3 = function (): void { // @ts-expect-error return true; };
对象类型
-
对象类型可以使用匿名的对象类型,接口,类型别名
-
属性修饰符
- 可选属性:属性名后加一个问号
?
,可选值不传入的时候会是undefined
,可以借助结构设置默认值,也可以在操作前判断避免bug
- 只读属性:在属性名前边加一个
readonly
,针对基本数据类型是不可设置值的,对于引用数据类型,里边属性可以更新,但是引用地址不能变 - 索引签名:不知道对象类型中的全部属性名字但是知道特点时使用
- 同时支持 string 和 number 类型,但数字索引的返回类型一定要是字符索引返回类型的子类型。这是因为当使用一个数字进行索引的时候,JavaScript 实际上把它转成了一个字符串。这就意味着使用数字 100 进行索引跟使用字符串 100 索引,是一样的
interface Animal { name: string; } interface Dog extends Animal { breed: string; } // Error: indexing with a numeric string might get you a completely separate type of Animal! interface NotOkay { [x: number]: Animal; // 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'. [x: string]: Dog; }
- 可选属性:属性名后加一个问号
-
属性继承:使用
extends
继承另外一个接口:interface A extends B,C,D{}
-
交叉类型:
type ColorfulCircle = Colorful & Circle;
连结Colorful
和Circle
产生了一个新的类型,新类型拥有Colorful
和Circle
的所有成员。 -
接口继承与交叉类型:最原则性的不同就是在于冲突怎么处理,使用继承的方式,如果重写类型会导致编译错误,但交叉类型不会
interface Colorful { color: string; } interface ColorfulSub extends Colorful { color: number } // Interface 'ColorfulSub' incorrectly extends interface 'Colorful'. // Types of property 'color' are incompatible. // Type 'number' is not assignable to type 'string'. //------------------------------------------------------------------------ interface Colorful { color: string; } type ColorfulSub = Colorful & { color: number } //虽然不会报错,那 color 属性的类型是什么呢,答案是 never,取得是 string 和 number 的交集。
-
泛型对象类型:对对象的属性类型不确定的时候使用:
interface Box<Type> { contents: Type; } //当我们引用 Box 的时候,我们需要给予一个类型实参替换掉 Type: let box: Box<string>; //类型别名也是可以使用泛型的 type Box<Type> = { contents: Type; }; //类型别名不同于接口,可以描述的不止是对象类型,所以我们也可以用类型别名写一些其他种类的的泛型帮助类型。 type OrNull<Type> = Type | null; type OneOrMany<Type> = Type | Type[]; type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>; type OneOrManyOrNull<Type> = OneOrMany<Type> | null type OneOrManyOrNullStrings = OneOrManyOrNull<string>; type OneOrManyOrNullStrings = OneOrMany<string> | null
Array
类型:类似于上面的Box
类型,Array
本身就是一个泛型interface Array<Type> { /** * Gets or sets the length of the array. */ length: number; /** * Removes the last element from an array and returns it. */ pop(): Type | undefined; /** * Appends new elements to an array, and returns the new length of the array. */ push(...items: Type[]): number; // ... }
ReadonlyArray
类型:一个特殊类型,它可以描述数组不能被改变。更简短的写法readonly Type[]
。
-
元组类型:元组类型是另外一种
Array
类型,当你明确知道数组包含多少个元素,并且每个位置元素的类型都明确知道的时候,就适合使用元组类型。也支持readonly
。在大部分的代码中,元组只是被创建,使用完后也不会被修改,所以尽可能的将元组设置为readonly
是一个好习惯。
泛型
-
泛型就是用一个相同类型来关联两个或者更多的值
-
泛型的调用方式有两种,只有类型推断失败的时候才会明确输入类型:
function identity<Type>(arg: Type): Type { return arg; } // 明确输入类型 let output = identity<string>("myString"); // let output: string // 类型推断 let output = identity("myString"); // let output: string
-
泛型类型:借助泛型接口
interface GenericIdentityFn<Type> { (arg: Type): Type; } function identity<Type>(arg: Type): Type { return arg; } let myIdentity: GenericIdentityFn<number> = identity; //方式二 interface GenericIdentityFn { <Type>(arg: Type): Type; } function identity<Type>(arg: Type): Type { return arg; } let myIdentity: GenericIdentityFn = identity;
-
泛型类:类似于泛型接口。注意静态成员并不能使用类型参数。
class GenericNumber<NumType> { zeroValue: NumType; add: (x: NumType, y: NumType) => NumType; } let myGenericNumber = new GenericNumber<number>(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function (x, y) { return x + y; };
-
泛型约束:一直泛型中必须存在某些属性时使用
interface Lengthwise { length: number; } function loggingIdentity<Type extends Lengthwise>(arg: Type): Type { console.log(arg.length); // Now we know it has a .length property, so no more error return arg; }
- 在泛型约束中使用类型参数:声明一个类型参数,这个类型参数被其他类型参数约束。
举个例子,我们希望获取一个对象给定属性名的值,为此,我们需要确保我们不会获取 obj 上不存在的属性。所以我们在两个类型之间建立一个约束
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) { return obj[key]; } let x = { a: 1, b: 2, c: 3, d: 4 }; getProperty(x, "a"); getProperty(x, "m"); // Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.
- 在泛型中使用类类型:这里没太看懂,后续用到了再去看吧
- 在泛型约束中使用类型参数:声明一个类型参数,这个类型参数被其他类型参数约束。
举个例子,我们希望获取一个对象给定属性名的值,为此,我们需要确保我们不会获取 obj 上不存在的属性。所以我们在两个类型之间建立一个约束
keyof
操作符
- 对一个对象类型使用
keyof
操作符,会返回该对象属性名组成的一个字符串或者数字字面量的联合。type Point = { x: number; y: number }; type P = keyof Point; //"x" | "y" type Arrayish = { [n: number]: unknown }; type A = keyof Arrayish; // type A = number type Mapish = { [k: string]: boolean }; type M = keyof Mapish;// type M = string | number 因为 JavaScript 对象的属性名会被强制转为一个字符串,所以 obj[0] 和 obj["0"] 是一样的。
- 对类和接口也可以使用
keyof
typeof
操作符
- 用于获取一个变量或者属性的类型
- 仅仅用来判断基本的类型,自然是没什么太大用,和其他的类型操作符搭配使用才能发挥它的作用。
举个例子:比如搭配
TypeScript
内置的ReturnType<T>
。你传入一个函数类型,ReturnType<T>
会返回该函数的返回值的类型function f() { return { x: 10, y: 3 }; } type P = ReturnType<typeof f>; // type P = { // x: number; // y: number; // }
索引访问类型
使用索引访问类型查找另外一个类型上的特定属性:
type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"];
// type Age = number
type I1 = Person["age" | "name"];
// type I1 = string | number
type I2 = Person[keyof Person];
// type I2 = string | number | boolean
type AliveOrName = "alive" | "name";
type I3 = Person[AliveOrName];
// type I3 = string | boolean
作为索引的只能是类型,这意味着你不能使用 const 创建一个变量引用:
const key = "age";
type Age = Person[key];
// Type 'key' cannot be used as an index type.
// 'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?
// 然而你可以使用类型别名实现类似的重构:
type key = "age";
type Age = Person[key];
条件类型
-
类似于
js
中的三元表达式,根据表达式结果返回不同的类型:interface Animal { live(): void; } interface Dog extends Animal { woof(): void; } type Example1 = Dog extends Animal ? number : string; // type Example1 = number type Example2 = RegExp extends Animal ? number : string; // type Example2 = string
-
可以使用条件类型简化函数重载
// 未使用条件类型 interface IdLabel { id: number /* some fields */; } interface NameLabel { name: string /* other fields */; } function createLabel(id: number): IdLabel; function createLabel(name: string): NameLabel; function createLabel(nameOrId: string | number): IdLabel | NameLabel; function createLabel(nameOrId: string | number): IdLabel | NameLabel { throw "unimplemented"; } // 使用条件类型 type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel;
-
条件类型约束:
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;
-
在条件类型里推断:提供了
infer
关键词,可以从正在比较的类型中推断类型,然后在true
分支里引用该推断结果。type Flatten<Type> = Type extends Array<infer Item> ? Item : Type; // 举个例子,我们可以获取一个函数返回的类型: type GetReturnType<Type> = Type extends (...args: never[]) => infer Return ? Return : never; // 当从多重调用签名(就比如重载函数)中推断类型的时候,会按照最后的签名进行推断,因为一般这个签名是用来处理所有情况的签名 declare function stringOrNum(x: string): number; declare function stringOrNum(x: number): string; declare function stringOrNum(x: string | number): string | number; type T1 = ReturnType<typeof stringOrNum>; // type T1 = string | number
-
分发条件类型:使用条件类型时传入一个联合类型,就会变成分发的
type ToArray<Type> = Type extends any ? Type[] : never; type StrArrOrNumArr = ToArray<string | number>; // type StrArrOrNumArr = string[] | number[]
让我们分析下
StrArrOrNumArr
里发生了什么,这是我们传入的类型:string | number
;接下来遍历联合类型里的成员,相当于:ToArray<string> | ToArray<number>
所以最后的结果是:string[] | number[]
通常这是我们期望的行为,如果你要避免这种行为,你可以用方括号包裹 extends 关键字的每一部分:
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never; // 'StrArrOrNumArr' is no longer a union. type StrArrOrNumArr = ToArrayNonDist<string | number>; // type StrArrOrNumArr = (string | number)[]
映射类型
-
一个类型需要基于另外一个类型,但是又不想拷贝一份,这个时候可以考虑使用映射类型
type OptionsFlags<Type> = { [Property in keyof Type]: boolean; }; type FeatureFlags = { darkMode: () => void; newUserProfile: () => void; }; type FeatureOptions = OptionsFlags<FeatureFlags>; // type FeatureOptions = { // darkMode: boolean; // newUserProfile: boolean; // }
-
有两个可能遇到的映射修饰符,一个是 readonly,用于设置属性只读,一个是 ? ,用于设置属性可选。可以通过前缀 - 或者 + 删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀。
// 删除属性中的只读属性 type CreateMutable<Type> = { -readonly [Property in keyof Type]: Type[Property]; }; type LockedAccount = { readonly id: string; readonly name: string; }; type UnlockedAccount = CreateMutable<LockedAccount>; // type UnlockedAccount = { // id: string; // name: string; // } // 删除属性中的可选属性 type Concrete<Type> = { [Property in keyof Type]-?: Type[Property]; }; type MaybeUser = { id: string; name?: string; age?: number; }; type User = Concrete<MaybeUser>; // type User = { // id: string; // name: string; // age: number; // }
-
通过
as
实现键名重新映射type Getters<Type> = { [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property] }; interface Person { name: string; age: number; location: string; } type LazyPerson = Getters<Person>; // type LazyPerson = { // getName: () => string; // getAge: () => number; // getLocation: () => string; // }
模板字面量类型
- 类似
js
中的模板字符串:
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"
类
-
没有类型注解会被隐式设置为
any
-
字段设置初始值会被用于推断类型,比如初始值是0,那么类型会被推断为
number
-
--strictPropertyInitialization
用于控制类字段是否需要在构造函数中初始化,但是如果在构造函数中调用另一个函数进行字段的初始化是不会被ts
识别的 -
readonly
修饰符:阻止字段在构造函数之外被被赋值 -
构造函数签名和普通函数类似,区别就在于:
- 构造函数不能有类型参数(关于类型参数,回想下泛型里的内容)。
- 构造函数不能有返回类型注解,因为总是返回类实例类型
-
ts
会提醒super
的调用 -
Getters / Setter
:- 如果 get 存在而 set 不存在,属性会被自动设置为 readonly
- 如果 setter 参数的类型没有指定,它会被推断为 getter 的返回类型
- getters 和 setters 必须有相同的成员可见性(Member Visibility)
-
implements
语句检查一个类是否满足一个特定的interface
:class Sonar implements interfaceA,interface2{}
-
成员可见性:
-
public
:以在任何地方被获取 -
protected
:仅对子类可见 -
private
:不允许访问,即使是子类 -
static
:静态成员,和实例没有关系,通过类直接访问。静态成员同样可以使用public
protected
和private
这些可见性修饰符,也可以被继承 -
类静态块:写一系列有自己作用域的语句,也可以获取类里的私有字段。
class Foo { static #count = 0; get count() { return Foo.#count; } static { try { const lastInstances = loadLastInstances(); Foo.#count += lastInstances.length; } catch {} } }
-
-
泛型类:
class Box<Type> { contents: Type; constructor(value: Type) { this.contents = value; } } const b = new Box("hello!"); // const b: Box<string>
转载自:https://juejin.cn/post/7218797742641610810