Typescript随笔
基本类型:string, boolean, number, null, undefined, void, never, enum, unknown, any, object, 数组, 元组。(null, undefined是除never外其他类型的子类型)
enum Color {R = 0, G = 1, B = 2}
let a: string | number | boolean | void | never | unknown | any | string[] | object | [string, number] | Color
a = null
a = undefined
使用never让编译器来检查穷举性的类型:
type NetworkLoadingState = {
state: "loading";
};
type NetworkFailedState = {
state: "failed";
code: number;
};
type NetworkSuccessState = {
state: "success";
response: {
title: string;
duration: number;
summary: string;
};
};
type NetworkOtherState = {
state: "other";
description: "other networkState";
}
type NetworkState =
| NetworkLoadingState
| NetworkFailedState
| NetworkSuccessState
| NetworkOtherState;
function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}
function logger(s: NetworkState): string {
switch (s.state) {
case "loading":
return "loading request";
case "failed":
return `failed with code ${s.code}`;
case "success":
return "got response";
default:
return assertNever(s); // Argument of type 'NetworkOtherState' is not assignable to parameter of type 'never'.
}
}
TypeScript的核心原则之一:类型检查重点在于值的形状(基于结构类型的而不是名义类型);且形状匹配只需要对象字段的子集匹配即可:
interface Point { x: number; y: number; } function printPoint(p: Point) { console.log(`${p.x}, ${p.y}`); } const point1 = { x: 1, y: 2 }; const point2 = { x: 1, y: 2, z: 3 }; printPoint(point1); printPoint(point2);
注:普通情况下子集就可以,但是,如果将对象字面量分配给其他变量或将其作为参数传递时,将对其进行特殊处理并进行过多的属性检查(Excess Property Checks)。如果对象字面量具有“目标类型”所不具有的任何属性,则会出现错误;可以使用类型断言/索引签名处理这种情况,如下:
类型断言需要开发确保自己的参数是正确的:
function printA(o: { a: string }) { console.log(o.a); } let myObj = { a: "a", b: 1 }; printA(myObj); // 正常 printA({ a: "a", b: 1 }); // Argument of type '{ a: string; b: number; }' is not assignable to parameter of type '{ a: string; }'. printA(<{ a: string }>{ a: "a", b: 1 }); // 正常 let o: { a: string }; o = { a: 'a' }; // 正常 o = { a: 'a', b: 1 }; // Type '{ a: string; b: number; }' is not assignable to type '{ a: string; }'. o = { a: 'a', b: 1 } as { a: string }; // 正常
索引签名中索引的属性必须是其他属性的超集:
interface Test { testA: string; testB: number; [propName: string]: string | number; }
function printA(o: { a: string, [propName: string]: any }) { console.log(o.a); } let myObj = { a: "a", b: 1 }; printA(myObj); // 正常 printA({ a: "a", b: 1 }); // 正常 let o: { a: string, [propName: string]: any }; o = { a: 'a' }; // 正常 o = { a: 'a', b: 1 }; // 正常
支持两种类型的索引签名:字符串和数字。可以同时支持两种类型的索引器,但是从数字索引器返回的类型必须是从字符串索引器返回的类型的子类型。这是因为当使用number编制索引时,JavaScript实际上会在编入对象之前将其转换为string。这意味着,索引100(number)与索引"100"(string)是同样的事情,所以这两个要一致。
interface Animal { name: string; } interface Dog extends Animal { breed: string; } interface NotOkay { [x: number]: Animal; // Numeric index type 'Animal' is not assignable to string index type 'Dog'. [x: string]: Dog; } interface Okay { [x: number]: Dog; // 正常 [x: string]: Animal; } interface TestNotOkay { [x: number]: number; // Numeric index type 'number' is not assignable to string index type 'string'. [x: string]: string; } interface Test { x: number; [propName: string]: number | string; // 正常 } interface TestOkay { [x: number]: number; [x: string]: number | string; // 正常 }
当操作类和接口的时候,类是具有两个类型:静态部分的类型和实例的类型。 当用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误,因为当一个类实现了一个接口时,只对实例部分进行了类型检查。 constructor存在于类的静态部分,所以不在检查的范围内:
interface ClockConstructor { new(hour: number, minute: number); } class Clock implements ClockConstructor { // Type 'Clock' provides no match for the signature 'new (hour: number, minute: number): any'. currentTime: Date; constructor(h: number, m: number) { } }
可以使用工厂函数和二次赋值的方式处理:
// 工厂函数 interface ClockInterface { tick(): void; } class DigitalClock implements ClockInterface { constructor(h: number, m: number) { } tick() { console.log("beep beep"); } } interface ClockConstructor { new(hour: number, minute: number): ClockInterface; } function createClock( ctor: ClockConstructor, hour: number, minute: number ): ClockInterface { return new ctor(hour, minute); } const digital = createClock(DigitalClock, 11, 11);
// 二次赋值 interface ClockConstructor { new(hour: number, minute: number): ClockInterface; } interface ClockInterface { tick(): void; } const Clock: ClockConstructor = class Clock implements ClockInterface { constructor(h: number, m: number) { } tick() { console.log("beep beep"); } }; const clock = new Clock(11, 11)
接口继承了一个类类型时,它会继承类的成员但不包括其实现。 接口会继承到类的private和protected成员,当创建一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现:
class Parent { private state: string; // 私有成员state } interface TestInterface extends Parent { select(): void; } class Child_A implements TestInterface { // Types have separate declarations of a private property 'state'. private state: string; select() { } } class Child_B extends Parent implements TestInterface { // 继承之后再实现接口正常 select() { } }
函数的重载,重载函数实现中的类型是其他函数定义中的类型(参数的类型和返回结果的类型)的超集
function test_A(x: number): number; function test_A(x: string): string; function test_A(x: string | number): string | number { if (typeof x === "string") { return "x is string" } else if (typeof x === "number") { return 1 } } function test_B(x: string): string; // 正常 function test_B(x: number): number; // This overload signature is not compatible with its implementation signature. function test_B(x: boolean): string; // This overload signature is not compatible with its implementation signature. function test_B(x: string | number): string { if (typeof x === "string") { return "x is string" } }
交叉类型:将多个类型合并为一个类型。 把现有的多种类型叠加到一起成为一种类型,包含了所需的所有类型的特性,如果有重复属性则取交集,取不到时转换为never类型
interface Test_A { a: string; b: number; } interface Test_B { x: string; y: number; } interface Test_C { a: string | boolean; c: number; } interface Test_D { a: boolean; d: number; } type Test_AB = Test_A & Test_B const ab: Test_AB = { a: 'a', b: 1, x: 'x', y: 2, } type Test_AC = Test_A & Test_C // 属性a被合并成string类型 const ac: Test_AC = { a: 'a', b: 1, c: 2, } type Test_AD = Test_A & Test_D // 属性a被合并成never类型 const ad: Test_AD = { a: 'a', // Type 'string' is not assignable to type 'never'. b: 1, d: 2, }
联合类型:取多个类型中的一部分,且对对象字面量不在进行过多的属性检查(Excess Property Checks)
interface Test_A { a: string; b: number; } interface Test_B { x: string; y: number; } type Test_BA = Test_A | Test_B let ba: Test_BA = { a: 'a', b: 1, x: 'x', y: 2, } ba = { a: 'a', b: 1, } ba = { x: 'x', y: 2, } ba = { // Type '{ a: string; x: string; }' is not assignable to type 'Test_BA'. a: 'a', x: 'x', } const baTest: Test_A = { a: 'a', b: 1, x: 'x', // Object literal may only specify known properties, and 'x' does not exist in type 'Test_A'. y: 2, // Object literal may only specify known properties, and 'y' does not exist in type 'Test_A'. }
泛型:类型变量,是一种特殊的变量,适用于类型而不是值,相比与any和联合类型有更严格的校验
function test(arg: string): string; function test(arg: number): number; function test(arg: boolean): boolean; function test(arg: string | number | boolean): string | number | boolean { // return true; // 返回string、number、boolean任意一种都可以并不能做到和入参类型一样 return arg; } test('test'); test(1); test(true); test({ test: 'test' }); // Argument of type '{ test: string; }' is not assignable to parameter of type 'string | number | boolean'. function testGenerics<T>(arg: T): T { // return true; // 'true' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'. return arg; } testGenerics<string>('test'); testGenerics<number>(1); testGenerics<boolean>(true); testGenerics<{ test: string }>({ test: 'test' });
泛型约束:希望这个泛型参数具有某些共性,使用extends关键字来实现约束
function test_A<T>(arg: T): T { console.log(arg.length); // Property 'length' does not exist on type 'T'. return arg; } function test_B<T extends { length: number; }>(arg: T): T { console.log(arg.length); // 正常 return arg; } test_B(0); // Argument of type '0' is not assignable to parameter of type '{ length: number; }'. test_B('0'); // 具有length属性,正常
keyof关键字:假设T是一个类型,那么keyof T产生的类型是T的属性名称字符串字面量类型构成的联合取值;如果T是一个带有索引签名的类型,且索引属性不为数字,那么keyof T是string和number的联合类型;如果索引属性为数字,keyof T 为number;如果有数字作为索引属性的同时还有其他固定属性,keyof T为number类型和其他字符串字面量的联合取值:
interface Test { a: string; b: number; c: boolean; } type TestKeyof = keyof Test; // test_A的取值为 "a" | "b" | "c" const test_A: TestKeyof = 'd' // Type '"d"' is not assignable to type '"a" | "b" | "c"'.
interface Test { a: string; b: number; c: boolean; [propName: string]: any; } type TestKeyof = keyof Test; // test_A的类型为string | number const test_A: TestKeyof = true // Type 'true' is not assignable to type 'string | number'.
interface Test1 { [propName: number]: any; } type Test1Keyof = keyof Test1; const test_A: Test1Keyof = true // Type 'boolean' is not assignable to type 'number'. interface Test2 { a: string; b: number; c: boolean; [propName: number]: any; } type Test2Keyof = keyof Test2; const test_B: Test2Keyof = true // Type 'true' is not assignable to type 'number | "a" | "b" | "c"'.
泛型约束配合keyof关键字使用可以构造出两个参数的依赖关系,比如用属性名从对象里获取这个属性,并且要确保这个属性存在于对象上:
function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; } let test = { a: 1, b: 2, c: 3, d: 4 }; getProperty(test, "a"); getProperty(test, "f"); // Argument of type '"f"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.
function getValues<T, P extends keyof T>(obj: T, keys: P[]): T[P][] { // 通过索引访问操作符T[P]动态得到返回值的类型 return keys.map(e => obj[e]); } let test = { a: 1, b: 'b', c: true, d: 4 }; const test1: string[] = getValues(test, ['a', 'd']); // Type 'number[]' is not assignable to type 'string[]'. const test2: string[] = getValues(test, ['a', 'b', 'c']); // Type '(string | number | boolean)[]' is not assignable to type 'string[]'.
Interface与Type:扩展字段的形式不同,Interface可以通过继承的方式和重写Interface的方式扩展,Type只能通过交叉类型的方式,不能重写:
interface Test_A { a: number } interface Test_B extends Test_A { b: string } const test_b: Test_B = { a: 1, b: 'b' } // 注释放开之后Test_A同时具有a和aa属性 Property 'aa' is missing in type '{ a: number; b: string; }' but required in type 'Test_B' // interface Test_A { // aa: string // }
type Test_A = { a: number } type Test_B = Test_A & { b: string } const test_b: Test_B = { a: 1, b: 'b', } type Test_A = { // Duplicate identifier 'Test_A'. aa: string }
映射类型:将一个类型映射成另外一个类型,在映射类型中,新类型以相同的方式转换旧类型中的每个属性,比如全部转为string或者readonly,也可以通过交叉类型继续扩展属性:
interface Test { a: string; b: number; c: boolean; } type TestString<T> = { [P in keyof T]: string } const test1: TestString<Test> = { // Type '{ a: string; }' is missing the following properties from type 'TestString<Test>': b, c a: 'a', c: true, // Type 'boolean' is not assignable to type 'string'. } type TestReadonly<T> = { readonly [P in keyof T]: T[P] } const test2: TestReadonly<Test> = { a: 'a', b: 1, c: true, } test2.a = 'aa'; // Cannot assign to 'a' because it is a read-only property. type TestExtend<T> = { [P in keyof T]: T[P] } & { d: number } const test3: TestExtend<Test> = { // Property 'd' is missing in type '{ a: string; b: number; c: true; }' but required in type '{ d: number; }'. a: 'a', b: 1, c: true, }