s一、交叉类型
表示一个对象类型的并集与交集
1,交叉类型是将多个类型合并为一个类型,定义的对象必须包含所有的类型,使用“&”表示
2,联合类型:如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员,使用“|”表示
举例:
interface ia {name: { attr1: string };}interface ib {name: { attr2: number };age: number;}// 交叉类型所有属性都要有const jiaocha: ia & ib = { name: { attr1: "11", attr2: 22 }, age: 16 };// 联合类型有其中一种属性就好了const lianhe: ia | ib = { name: { attr1: "1" } };
二、类型守卫与类型区分
如果想要访问这种json对象的方法swim不报错,有两种方法:
interface Bird {fly: () => void;layEggs: () => void;}interface Fish {swim: () => void;layEggs: () => void;}const getSmallPet = (): Fish | Bird => {const swim = () => {console.log("do swim");};const layEggs = () => {console.log("do layEggs");};return { swim, layEggs };};// 为什么需要断言保护:因为访问报错const pet = getSmallPet();pet.layEggs(); // 正常访问:因为是交集所以可以访问// pet.swim(); // 访问报错:交集之外,就访问不了,就需要用到类型保护区分类型
1,断言
// 优点,方便使用;缺点,冗余,如果少次访问的时候可以使用,且容易出错(pet as Fish).swim();
2,用户自定义的类型保护
const isFish = (pet: Fish | Bird): pet is Fish => {// 使用类型谓词,多次访问时使用// pet is Fish就是类型谓词。 parameterName必须是来自于当前函数签名里的一个参数名。return (<Fish>pet).swim !== undefined;};// 'swim' 和 'fly' 调用都没有问题了if (isFish(pet)) {pet.swim();} else {// 注意:TypeScript不仅知道在if分支里pet是Fish类型;它还清楚在else分支里,一定不是Fish类型,而是Bird类型。pet.fly();}
3,typeof类型保护
const padLeft = (value: string, padding: string | number) => {// 缺点,只能访问简单类型,比如string、number、boolen;优点,方便if (typeof padding === "number") {console.log(Number(padding) + value);}if (typeof padding === "string") {console.log(padding + value);}};padLeft(" fish", 1);padLeft(" fish", "one");
4,instanceof类型保护
interface Animal {name(): string;}class MyFish implements Animal {constructor(private numSpaces: number) {}name() {return String(this.numSpaces);}}class MyBird implements Animal {constructor(private value: string) {}name() {return this.value;}}function getRandomAnimal() {return Math.random() < 0.5 ? new MyFish(4) : new MyBird("fly Bird");}// 随机选一个类型,类型为SpaceRepeatingPadder | StringPadderlet padder: Animal = getRandomAnimal();if (padder instanceof MyFish) {const name = padder.name(); // 类型细化为'SpaceRepeatingPadder'console.log("如果是MyFish===》", name);}if (padder instanceof MyBird) {const name = padder.name(); // 类型细化为'StringPadder'console.log("如果是MyBird===》", name);}
三、可以为 null 的类型
类型检查器认为 null与 undefined可以赋值给任何类型。 null与 undefined是所有其它类型的一个有效值。
// 注意,按照JavaScript的语义,TypeScript会把 null和 undefined区别对待。 string | null, string | undefined和 string | undefined | null是不同的类型。let s = "foo";// s = null; // 错误, 'null'不能赋值给'string'let sn: string | null = "bar";sn = null; // 可以// sn = undefined; // 错误, 'undefined'不能赋值给'string | null'
1,可选参数和可选属性
a,可选参数会被自动地加上 | undefined:
const add = (x: number, y?: number) => {return x + (y || 0);};add(1, 2);add(1);add(1, undefined); // 直接设置undefined// add(1, null); // 错误, 'null' 不能赋值给 'number | undefined'
b,可选属性也会有同样的处理:
class MyClass {type?: number;}let myClass = new MyClass();myClass.type = 13;myClass.type = undefined; // ok// c.b = null; // 错误, 'null' 不属于 'number | undefined'
2,类型保护和类型断言
a,由于可以为null的类型是通过联合类型实现,那么你需要使用类型保护来去除 null。
const f1 = (sn: string | null): string => {if (sn == null) {return "default";} else {return sn;}};f1(null);const f2 = (sn: string | undefined): string => {return sn || "default";};
b,如果编译器不能够去除 null或 undefined,你可以使用类型断言手动去除。 语法是添加 !后缀: identifier!从 identifier的类型里去除了 null和 undefined:
const broken = (name: string | null): string => {const postfix = (epithet: string) => {//return name.charAt(0) + '. the ' + epithet; // 错误, 'name' 可能是 nullreturn name!.charAt(0) + ". the " + epithet; // 正确};name = name || "Bob";return postfix("great");};
四、类型别名
类型别名会给一个类型起个新名字。不会新建一个类型
// 起别名type Name = string;type NameResolver = () => string;type NameOrResolver = Name | NameResolver;function getName(n: NameOrResolver): Name {if (typeof n === 'string') {return n;}else {return n();}}// 可以使用泛型type Container<T> = { value: T };//引用自己type Tree<T> = {value: T;left: Tree<T>;right: Tree<T>;}// 与交叉类型一起使用type LinkedList<T> = T & { next: LinkedList<T> };interface Person {name: string;}var people: LinkedList<Person>;var s = people.name;var s = people.next.name;var s = people.next.next.name;var s = people.next.next.next.name;type Yikes = Array<Yikes>; // 错误,类型别名不能出现在声明右侧的任何地方。
1,接口和类型别名的区别
a,接口创建了一个新的名字,可以在其它任何地方使用。 类型别名并不创建新名字 — 比如,错误信息就不会使用别名。
type Alias = { num: number }interface Interface {num: number;}declare function aliased(arg: Alias): Alias; // 在编译器中将鼠标悬停在 aliased 上时,显示的是对象字面量类型。declare function interfaced(arg: Interface): Interface;// 在编译器中将鼠标悬停在 interfaced 上,显示它返回的是 Interface
b,另一方面,如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。
五、字符串字面量类型、数字字面量类型
1,字符串字面量类型允许你指定字符串必须的固定值。
// 1,实现类似枚举类型的字符串type Easing = "ease-in" | "ease-out" | "ease-in-out";class UIElement {animate(dx: number, dy: number, easing: Easing) {if (easing === "ease-in") {// ...}else if (easing === "ease-out") {}else if (easing === "ease-in-out") {}else {// error! should not pass null or undefined.}}}let button = new UIElement();button.animate(0, 0, "ease-in");button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here// 2,区分函数重载function createElement(tagName: "img"): HTMLImageElement;function createElement(tagName: "input"): HTMLInputElement;// ... more overloads ...function createElement(tagName: string): Element {// ... code goes here ...}
2,数字字面量类型很少用,但它们可以用在缩小范围,比如调试 bug 的时候:
function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {// ...}
六、枚举成员类型
如我们在枚举一节里提到的,当每个枚举成员都是用字面量初始化的时候枚举成员是具有类型的。
在我们谈及 “单例类型” 的时候,多数是指枚举成员类型和数字 / 字符串字面量类型,尽管大多数用户会互换使用 “单例类型” 和 “字面量类型”。
七、可辨识联合
你可以合并单例类型,联合类型,类型守卫和类型别名来创建一个叫做可辨识联合的高级模式,它也称做标签联合或代数数据类型
interface Square {kind: "square";size: number;}interface Rectangle {kind: "rectangle";width: number;height: number;}interface Circle {kind: "circle";radius: number;}type Shape = Square | Rectangle | Circle; // 以上的联合类型function area(s: Shape) {switch (s.kind) { // 找到可识别的属性case "square": return s.size * s.size;case "rectangle": return s.height * s.width;case "circle": return Math.PI * s.radius ** 2;}}
1,完整性检查
type Shape = Square | Rectangle | Circle | Triangle;function area(s: Shape) {switch (s.kind) {case "square": return s.size * s.size;case "rectangle": return s.height * s.width;case "circle": return Math.PI * s.radius ** 2;}// 这样就会出错,因为 case 少了"Triangle"}// 解决方法1: 启用 --strictNullChecks 并指定一个返回值类型function area(s: Shape): number | undefined {···}// 解决方法2:使用 never 类型function assertNever(x: never): never {throw new Error("Unexpected object: " + x);}function area(s: Shape) {switch (s.kind) {case "square": return s.size * s.size;case "rectangle": return s.height * s.width;case "circle": return Math.PI * s.radius ** 2;default: return assertNever(s); // assertNever 检查 s 是否为 never 类型 — 即为除去所有可能情况后剩下的类型}}
八、多态的 this 类型
多态的 this 类型表示的是某个包含类或接口的子类型。方便链式调用
class BasicCalculator {public constructor(protected value: number = 0) { }public currentValue(): number {return this.value;}public add(operand: number): this {this.value += operand;return this;}public multiply(operand: number): this {this.value *= operand;return this;}}let v = new BasicCalculator(2).multiply(5).add(1).currentValue();// 由于这个类使用了 this 类型,你可以继承它,新的类可以直接使用之前的方法,不需要做任何的改变。class ScientificCalculator extends BasicCalculator {public constructor(value = 0) {super(value);}public sin() {this.value = Math.sin(this.value);return this;}}let v = new ScientificCalculator(2).multiply(5).sin().add(1).currentValue();
九、索引类型
使用索引类型,编译器就能够检查使用了动态属性名的代码
// 1,keyof T :索引类型查询操作符// 2,T[K] :索引访问操作符:确保类型变量 K extends keyof T 就可以使用了function pluck<T, K extends keyof T>(o: T, propertyNames: K[]): T[K][] {return propertyNames.map(n => o[n]);}// let carProps: keyof Car; // 相当于 let carProps: 'manufacturer' | 'model' | 'year'interface Car {manufacturer: string;model: string;year: number;}let taxi: Car = {manufacturer: 'Toyota',model: 'Camry',year: 2014};// Manufacturer 和 model 都是字符串类型,所以我们可以直接定义为字符串类型let makeAndModel: string[] = pluck(taxi, ['manufacturer', 'model']);//如果是 model 和 year, 我们可以定义类型为 (string | number)[]let modelYear = pluck(taxi, ['model', 'year'])
1,索引类型和字符串索引签名
索引签名的参数类型必须为 number 或 string
interface Dictionary<T> {[key: string]: T; // string}let keys: keyof Dictionary<number>; // string | number 因为你可以使用字符串 object['42'] 或 数字 object[42] 索引来访问对象属性。let value: Dictionary<number>['foo']; // numberinterface Dictionary<T> {[key: number]: T; // number}let keys: keyof Dictionary<number>; // numberlet value: Dictionary<number>['foo']; // 错误,索引为数字类型,不能是字符串“foo”let value: Dictionary<number>[42]; // number
十、映射类型
TypeScript 提供了从旧类型中创建新类型的一种方式 — 映射类型
// 例子:把一个普通的接口,转变成一个只读的接口// 1,定义原始数据类型interface Person {name: string;age: number;}// 2,定义映射方法// 内部使用了for .. in,具有三个部分:// a,类型变量 P ,它会依次绑定到每个属性。// b,字符串字面量联合的 (keyof T) ,它包含了要迭代的属性名的集合。// 相当于:'option1' | 'option2';// c,T[P] 属性的结果类型。type Readonly<T> = {readonly [P in keyof T]: T[P];}// 3,使用映射方法,获得新的类型,该类型只能读取type ReadonlyPerson = Readonly<Person>;// 上面的想相当于:// interface PersonReadonly {// readonly name: string;// readonly age: number;// }
若想在映射的同时添加新成员,则可使用交叉类型
type Readonly<T> = {readonly [P in keyof T]: T[P];}& {readonly name: string }
1,由映射类型进行推断
function unproxify<T>(t: Proxify<T>): T {let result = {} as T;for (const k in t) {result[k] = t[k].get();}return result;}let originalProps = unproxify(proxyProps);
2,有条件类型
它能够表示非统一的类型。 有条件的类型会以一个条件表达式进行类型关系检测,从而在两种类型中选择其一:
T extends U ? X : Y // 若 T 能够赋值给 U ,那么类型是 X ,否则为 Y 。
a,分布式有条件类型
分布式有条件类型在实例化时会自动分发成联合类型。
// 1,type TypeName<T> =T extends string ? "string" :T extends number ? "number" :T extends boolean ? "boolean" :T extends undefined ? "undefined" :T extends Function ? "function" :"object";type T10 = TypeName<string | (() => void)>; // "string" | "function"type T12 = TypeName<string | string[] | undefined>; // "string" | "object" | "undefined"type T11 = TypeName<string[] | number[]>; // "object"// 2,type BoxedValue<T> = { value: T };type BoxedArray<T> = { array: T[] };type Boxed<T> = T extends any[] ? BoxedArray<T[number]> : BoxedValue<T>;type T20 = Boxed<string>; // BoxedValue<string>;type T21 = Boxed<number[]>; // BoxedArray<number>;type T22 = Boxed<string | number[]>; // BoxedValue<string> | BoxedArray<number>;// 3,type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;interface Part {id: number;name: string;subparts: Part[];updatePart(newName: string): void;}type T40 = FunctionPropertyNames<Part>; // "updatePart"type T41 = NonFunctionPropertyNames<Part>; // "id" | "name" | "subparts"type T42 = FunctionProperties<Part>; // { updatePart(newName: string): void }type T43 = NonFunctionProperties<Part>; // { id: number, name: string, subparts: Part[] }
b,有条件类型中的类型推断
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;// infer R ? R : any 表示如果推断出类型,成功就返回被推断的类型,要不然就是never// 无法在正常类型参数的约束子语句中使用 infer 声明type ReturnType<T extends (...args: any[]) => infer R> = R; // 错误,不支持type AnyFunction = (...args: any[]) => any;type ReturnType<T extends AnyFunction> = T extends (...args: any[]) => infer R ? R : any;// 嵌套,按顺序进行求值type Unpacked<T> =T extends (infer U)[] ? U :T extends (...args: any[]) => infer U ? U :T extends Promise<infer U> ? U :T;type T0 = Unpacked<string>; // stringtype T1 = Unpacked<string[]>; // stringtype T2 = Unpacked<() => string>; // stringtype T3 = Unpacked<Promise<string>>; // stringtype T4 = Unpacked<Promise<string>[]>; // Promise<string>type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string// 协变位置上,同一个类型变量的多个候选类型会被推断为联合类型:type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;type T10 = Foo<{ a: string, b: string }>; // stringtype T11 = Foo<{ a: string, b: number }>; // string | number // 联合类型// 抗变位置上,同一个类型变量的多个候选类型会被推断为交叉类型:type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>; // stringtype T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number // 交叉类型
c,预定义的有条件类型
TypeScript 2.8 在 lib.d.ts 里增加了一些预定义的内置的有条件类型:
// Exclude<T, U> -- 从 T 中剔除可以赋值给 U 的类型。// Extract<T, U> -- 提取 T 中可以赋值给 U 的类型。// NonNullable<T> -- 从 T 中剔除 null 和 undefined 。// ReturnType<T> -- 获取函数返回值类型。// InstanceType<T> -- 获取构造函数类型的实例类型。type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"type T02 = Exclude<string | number | (() => void), Function>; // string | numbertype T03 = Extract<string | number | (() => void), Function>; // () => voidtype T04 = NonNullable<string | number | undefined>; // string | numbertype T05 = NonNullable<(() => string) | string[] | null | undefined>; // (() => string) | string[]function f1(s: string) {return { a: 1, b: s };}class C {x = 0;y = 0;}type T10 = ReturnType<() => string>; // stringtype T11 = ReturnType<(s: string) => void>; // voidtype T12 = ReturnType<(<T>() => T)>; // {}type T13 = ReturnType<(<T extends U, U extends number[]>() => T)>; // number[]type T14 = ReturnType<typeof f1>; // { a: number, b: string }type T15 = ReturnType<any>; // anytype T16 = ReturnType<never>; // nevertype T17 = ReturnType<string>; // Errortype T18 = ReturnType<Function>; // Errortype T20 = InstanceType<typeof C>; // Ctype T21 = InstanceType<any>; // anytype T22 = InstanceType<never>; // nevertype T23 = InstanceType<string>; // Errortype T24 = InstanceType<Function>; // Error
