TypeScript
提供了很多强大的类型别名,通过这些类型别名,可以方便将一种类型转换为另一种类型,本文主要作为常用类型别名的速查及基本的使用。
学习常用的类型别名之前,先来看一些TypeScript
中的关键字。
TypeScript中的关键字
extends
T extends U ? X : Y
复制代码
很明显,它类似于 js 中的三目运算符。事实上,确实可以理解为三目运算符 。若T
能够赋值给U
,那么类型是X
,否则为Y
。官网的例子:
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
复制代码
extends
的使用时,如果 T
为联合类型,则会起来类似于 for
循环分解参数的作用,如果 T
的类型为 A | B | C
,会被解析为(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
。因此可以方便实现过滤联合类型,官网的例子如下:
type Diff<T, U> = T extends U ? never : T; // Remove types from T that are assignable to U
type T30 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
type Filter<T, U> = T extends U ? T : never; // Remove types from T that are not assignable to U
type T31 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"
复制代码
keyof
keyof
关键字用来返回某种类型的所有键,其结果为联合类型
interface Person {
name: string,
age: number
}
type K1 = keyof Person // "name" | "age"
type K2 = keyof any[]; // number | "length" | "toString" | "toLocaleString" | "pop" | "push" | "concat" | "join" | "reverse" | "shift" | "slice" | "sort" | "splice" | "unshift" | "indexOf" | "lastIndexOf" | ... 13 more ... | "values"
type Mapish = { [k: string]: boolean };
type M = keyof Mapish; // string | number 此处返回 string | number 的原因是 JavaScript 中始终强制将对象的键转换为字符串,因此 obj[0] 和 obj['0'] 始终一致
class Person1 {
name = 'xiaoping'
age: number
constructor(age: number) {
this.age = age
}
say() {
console.log(`my name is ${this.name}`)
}
}
type P = keyof Person1 // "name" | "age" | "say"
复制代码
typeof
在 JavaScript
中 typeof
是一个可以在表达式中使用的运算符。在 TypeScript
中的 typeof
可以用来获取一个变量和属性的类型。
let s = "hello";
let n: typeof s; // string
let person = {
name: 'xiaoping',
age: 19,
address: {
province: "上海"
}
}
type P = typeof person
/* 相当于
type P = {
name: string;
age: number;
address: {
province: string;
};
}
*/
function userInfo(age) {
return {
name: 'xiaoping',
age: age
}
}
type Fn = typeof userInfo
// type Fn = (age: any) => { name: string; age: any; }
复制代码
infer
infer
用来代表一个待推断的类型变量。这个关键字比较难理解,我们直接来看例子。
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
const add = (x:number, y:number) => x + y
type t = ReturnType<typeof add> // type t = number
复制代码
ReturnType
代表如果 T
可以赋值给 (...args: any[]) => infer R
,则返回 R
否则返回 any
。(...args: any[]) => infer R
代表的是参数是任意值,返回值是inter R
的函数。 很明显 add
是符合的,且add
函数的返回值是 number
。因此infer R
为 number
类型。 type t = number
inter
不仅仅用来推断返回值,它最强大的功能在于拆包。官网的例子如下:
type Unpacked<T> =
T extends (infer U)[] ? U :
T extends (...args: any[]) => infer U ? U :
T extends Promise<infer U> ? U :
T;
复制代码
此处看起来比较难以理解,我们拆开查看:
type Unpacked<T> = T extends (infer U)[] ? U : T
type S1 = string[]
type T1 = Unpacked<S1>// string
复制代码
如果 T
可以赋值给 某个类型 (infer U)
的数组 (infer U)[]
则返回 U
,否则返回 T
。因此可以很方便的拆包获得 T1
为 string。
同理 Promise
的拆包,也可以很轻松的实现。
type Unpacked<T> = T extends Promise<infer U> ? U : T
type T1 = Unpacked<Promise<{ name: string, age: number }>> //{ name: string; age: number; }
复制代码
unknown
在 TypeScript
中,当我们不确定一个类型是什么类型的,可以选择给其声明为any
或者unkown
。但实际上,TypeScript
推荐使用unknown
,因为unknown
是类型安全的。如果是any
,你可以任意的取值赋值,不会进行任何的类型检查。但unkown
就不一样了,必须先进行断言 ,就是使用typeof
或 instanceof
来判断类型。之后才能进行操作。
is
is
关键字一般用于函数返回值类型中,判断参数是否属于某一类型,并根据结果返回对应的布尔类型
function isString(s: unknown): boolean {
return typeof s === 'string'
}
function toUpperCase(x: unknown) {
if (isString(x)) {
x.toUpperCase() // Error, Object is of type 'unknown'
}
}
复制代码
上面的例子虽然已经判断了 s
是 string
类型时才会调用 toUpperCase
。但是依然会报错,这是由于嵌套函数使 TypeScript
不能做出正确的推断。这时就可以使用 is
关键字。
function isString(s: unknown): s is string {
return typeof s === 'string'
}
function toUpperCase(x: unknown) {
if (isString(x)) {
x.toUpperCase() // 此时不在报错,正常通过校验
}
}
复制代码
强大的内置类型别名
TypeScript
提供了一些强大的类型别名来实现常见的类型转换。
Partial<T>
Partial
可以将所有的属性变为可选类型。
interface Todo {
title: string,
description: string;
}
type PartialTodo = Partial<Todo>
/* 相当于
interface PartialTodo {
title?: string;
description?: string;
}
*/
复制代码
源码如下:
type Partial<T> = {
[P in keyof T]?: T[P];
};
复制代码
Required<T>
Required
和 Partial
的作用相反,可以将所有可选类型变为必须。
interface Props {
a?: number;
b?: string;
}
type RequireProps = Required<Props>
/* 相当于
interface RequireProps {
a: number;
b: string;
}*/
复制代码
源码如下:
type Required<T> = {
[P in keyof T]-?: T[P];
};
复制代码
Readonly
将所有的属性处理为 Readonly
。
interface Todo {
title: string,
description: string;
}
type ReadonlyTodo = Readonly<Todo>
/* 相当于
interface ReadonlyTodo {
readonly title: string,
readonly description: string;
}
*/
复制代码
源码如下:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
复制代码
Record<K,T>
构造一个对象类型,其属性为为 K
类型 ,属性值为 T
类型。常用于将一种类型属性映射为另一种类型。
interface CatInfo {
age: number;
breed: string;
}
type CatName = "miffy" | "boris" | "mordred";
type Cats = Record<CatName, CatInfo> // { miffy: CatInfo; boris: CatInfo; mordred: CatInfo; }
const cats: Cats = {
miffy: { age: 10, breed: "Persian" },
boris: { age: 5, breed: "Maine Coon" },
mordred: { age: 16, breed: "British Shorthair" },
}
复制代码
源码如下:
type Record<K extends keyof any, T> = {
[P in K]: T;
};
复制代码
Pick<T, K>
通过从 T
中选取一组属性 K
来构造一个新的类型
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">; //
/* 相当于
interface TodoPreview {
title: string;
completed: boolean;
}*/
复制代码
源码如下:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
复制代码
Exclude<T, E>
将 T
中所有可以分配给 E
的类型排除掉之后生成一个新的类型。
type T0 = Exclude<"a" | "b" | "c", "a">;
// 相当于 type T0 = "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
// 相当于 type T1 = "c"
type T2 = Exclude<string | number | (() => void), Function>;
// 相当于 type T2 = string | number
复制代码
源码如下:
type Exclude<T, U> = T extends U ? never : T;
复制代码
Omit<T, K>
从 T
中剔除一些属性 K
来生成新的类型
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}
type TodoPreview = Omit<Todo, "description" | "completed">;
const todo: TodoPreview = {
title: '',
createdAt: Date.now()
}
复制代码
源码如下:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
复制代码
Extract<T, U>
将 T
中所以可以分配给 U
的类型提取出来,生成一个新的类型。和 Exclude
相反
type T0 = Extract<"a" | "b" | "c", "a" | "f">;
// 相当于 type T0 = "a"
type T1 = Extract<string | number | (() => void), Function>;
// 相当于 type T1 = () => void
复制代码
源码如下:
type Extract<T, U> = T extends U ? T : never;
复制代码
NonNullable<T>
将 T
中的 null
和 undefined
排除掉。
type T0 = NonNullable<string | number | undefined>;
// 相当于 type T0 = string | number
type T1 = NonNullable<string[] | null | undefined>;
// 相当于 type T1 = string[]
复制代码
Parameters<T>
返回函数类型 T
的参数来构造成一个元组。
declare function f1(arg: { a: number; b: string }): void;
type T0 = Parameters<() => string>;
// type T0 = []
type T1 = Parameters<(s: string) => void>;
// type T1 = [s: string]
type T2 = Parameters<<T>(arg: T) => T>;
// type T2 = [arg: unknown]
type T3 = Parameters<typeof f1>;
// type T3 = [arg: {
// a: number;
// b: string;
// }]
type T4 = Parameters<any>;
// type T4 = unknown[]
type T5 = Parameters<never>;
// type T5 = never
复制代码
源码如下:
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
复制代码
ConstructorParameters<T>
生成一个由构造函数的参数构成的元组或者数组。如果 T
不是构造函数,返回 nerver
class Fn {
constructor(name: string, age: number) {
}
}
type A = ConstructorParameters<typeof Fn> // [name:string,age:number]
type T0 = ConstructorParameters<ErrorConstructor>; //[message?: string]
type T1 = ConstructorParameters<FunctionConstructor>; //string[]
复制代码
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
复制代码
ReturnType<T>
生成一个由函数 T
的返回值构成的类型。配合 typeof
获取函数的返回值类型
type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<(s: string) => void>; // void
type T2 = ReturnType<<T>() => T>;//unknown
type T3 = ReturnType<<T extends U, U extends number[]>() => T>;//number[]
function fn(){
return{
name:'xiaoping',
age:18
}
}
type T4 = ReturnType<typeof fn>;
// type T4 = {
// name: string;
// age: number;
// }
type T5 = ReturnType<any>; // any
type T6 = ReturnType<never>; // never
复制代码
源码如下:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
复制代码
InstanceType<T>
获取构造函数的实例类型。
class C {
x = 0;
y = 0;
}
type T20 = InstanceType<typeof C>; // C
type T21 = InstanceType<any>; // any
type T22 = InstanceType<never>; // any
复制代码
源码如下:
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
复制代码
ThisParameterType<T>
看这个类型别名之前,先来看一下 JavaScript
中怪异的 this
问题。
window.name = "hello world";
let person = {
name: "xiaoping",
say: function () {
console.log(`my name is ${this.name}`);
}
};
person.say(); //my name is xiaoping
const say = person.say;
say(); //my name is hello world
复制代码
由于 JavaScript
中的 this
永远指向的是 js
执行栈的栈顶,因此导致当我们用一个变量将 say
函数保存下来的时候,就会使得当前的 this
出现错误,获得非预期的结果。TypeScript
中就增加了一个 this
的参数限定。当函数执行的 this
不符合预期时抛出错误,如下:
interface Person {
name: string,
say: (this: Person) => void
}
const person: Person = {
name: 'xiaoping',
say: function () {
console.log(`my name is ${this.name}`)
}
}
person.say()
const say = person.say
// say() // // error The 'this' context of type 'void' is not assignable to method's 'this' of type 'Person'.
复制代码
ThisParameterType
就是用来获取函数的 this
function toHex(this: number) {
return this.toString(16);
}
type ToHexThis = ThisParameterType<typeof toHex> // number
function fn(value: string) {
console.log(value)
}
type A = ThisParameterType<typeof fn> //unknown
复制代码
源码如下:
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;
复制代码
OmitThisParameter<T>
如果一个函数被限定了 this
,返回一个剔除了 this
之后的函数类型。
function toHex(this: number) {
return this.toString(16);
}
type ToHexThis = ThisParameterType<typeof toHex> // number
type T1 = OmitThisParameter<typeof toHex> // () => string
type T2 = typeof toHex // (this: number) => string
const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5);
console.log(fiveToHex());
复制代码
源码如下:
type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T;
复制代码
ThisType<T>
它不返回任何转换过的类型,而是作为对象字面量上下文 this
的标识,并且要使用这个类型,需要启用配置 -noImplicitThis
。
官方例子如下:可以看出只是为了增加 this
的标识。
type ObjectDescriptor<D, M> = {
data?: D;
methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
};
function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
let data: object = desc.data || {};
let methods: object = desc.methods || {};
return { ...data, ...methods } as D & M;
}
let obj = makeObject({
data: { x: 0, y: 0 },
methods: {
moveBy(dx: number, dy: number) {
this.x += dx; // Strongly typed this
this.y += dy; // Strongly typed this
},
},
});
obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);
复制代码
源码的实现也只是简单的定义了一个空接口。
interface ThisType<T> { }
复制代码
内置的字符串操作类型
Uppercase<S>
Lowercase<S>
Capitalize<S>
Uncapitalize<S>
为了严格的限定字符串的操作,TypeScript
内置了一些字符串类型,对字符串进行限定。
type Cases<T extends string> = `${Uppercase<T>} ${Lowercase<T>} ${Capitalize<T>} ${Uncapitalize<T>}`;
type T11 = Cases<'bar'>; // 'BAR bar Bar bar'
复制代码
TypeScript中实用却容易忽略的方法
常量枚举
在 TypeScript
中,枚举会被编译成自执行函数,如下:
enum Direction {
Up = 1,
Down,
Left,
Right,
}
复制代码
"use strict";
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 1] = "Up";
Direction[Direction["Down"] = 2] = "Down";
Direction[Direction["Left"] = 3] = "Left";
Direction[Direction["Right"] = 4] = "Right";
})(Direction || (Direction = {}));
复制代码
这会增加大量的冗余代码,并且在获取值时,会增加一次值的获取过程。
常量枚举,会在编译时被完全删除,从而避免增加额外的开销。如下:
const enum Direction {
Up = 1,
Down,
Left,
Right,
}
let a = Direction.Up
复制代码
"use strict";
let a = 1 /* Up */;
复制代码
合并类和接口
通过接口声明来扩展类的实例类型. 类构造函数对象不会被修改
declare class Foo {
public x : number;
}
interface Foo {
y : string;
}
function bar(foo : Foo) {
foo.x = 1; // 没问题, 在类 Foo 中有声明
foo.y = "1"; // 没问题, 在接口 Foo 中有声明
}
复制代码
增强现有的模块
通过 declare module "foo" { }
对现有的模块进行增强。官网例子如下:
/ observable.ts
export class Observable<T> {
// ...
}
// map.ts
import { Observable } from "./observable";
// 扩充 "./observable"
declare module "./observable" {
// 使用接口合并扩充 'Observable' 类的定义
interface Observable<T> {
map<U>(proj: (el: T) => U): Observable<U>;
}
}
Observable.prototype.map = /*...*/;
// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map(x => x.toFixed());
复制代码
同样的,也可以通过 declare global
声明从模块中扩展全局范围
// Ensure this is treated as a module.
export {};
declare global {
interface Array<T> {
mapToNumbers(): number[];
}
}
Array.prototype.mapToNumbers = function () {
/* ... */
};
复制代码
后期学习过程中,继续补充。