写在前面
使用 typescript也有一段时间了,这里分享一些typescript里类型定义的小技巧
关键字
any & unknown
any 是typescript 的顶级类型。unknown 是any对应的安全类型。
在项目使用中,所有的any都可以使用unknown代替。
let value: any;
// any 的赋值
value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(); // OK
value = Symbol("type"); // OK
// any 的调用
value.foo.bar; // OK
value.trim(); // OK
value(); // OK
new value(); // OK
value[0][1]; // OK
let value: unknown;
// unknown 的赋值,只能将 unknown 类型的变量赋值给 any 和 unknown
let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error
// unknown 的调用
value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error
unknown 在使用时必须要明确类型
const arr: unknown = [1, 2, 3];
(arr as Array<number>).join(',');
if (Array.isArray(arr)) {
arr.join(',');
}
interface & type
一般来说,interface 与 type 区别很小,比如以下两种写法差不多
interface A {
a: number;
b: number;
};
type B = {
a: number;
b: number;
}
其中 interface 可以如下合并多个,而 type 只能使用 & 类进行连接。
interface A {
a: number;
}
interface A {
b: number;
}
const a: A = {
a: 3,
b: 4
}
interface 和 type 都可以用来定义继承的类。
举个例子,实现一个注册自定义事件的功能
// 定义基础 事件 类型
class Action {
name: string = 'action';
constructor() {
this.init();
}
init() {
}
apply() {
}
}
type ActionChildClass = new() => Action;
// 等价于 type ActionChildClass = typeof Action;
// or
// interface 可以新增其他属性,
interface ActionChildClass {
otherPrototype: string;
new(): Action;
}
// 注册自定义事件的函数,参数 ActionClass 就是继承自 Action 的自定义事件
function registerAction(ActionClass: ActionChildClass) {
// 实现逻辑
}
registerAction 函数实现注册自定义事件的功能,参数是继承 Action 的类。
使用 type 与 interface 都能实现,interface 还可以新增其他属性,React 里面 ComponentClass 就是使用这种方式定义的。
interface 还可以定义函数:
interface AddFn {
(a: number, b: number): number;
}
// 等价于
type AddFn = (a: number, b: number) => number;
一般使用在定义重载的函数,例如 Array 的 splice 方法
interface Array<T> {
splice(start: number, deleteCount?: number): T[];
splice(start: number, deleteCount: number, ...items: T[]): T[];
}
单纯的函数重载也可以这样写:
// 使用 interface
interface AddFn {
(a: number, b: number): number;
(a: number, b: number, c: number, d: number): number;
}
const add1: AddFn = function () {
return [...arguments].reduce((a, b) => a + b, 0);
};
add1(1, 2); // OK
add1(1, 2, 3); // Error
add1(1, 2, 3, 4); // OK
// 使用 function
function add(a: number, b: number): number;
function add(a: number, b: number, c: number, d: number): number;
function add() {
return [...arguments].reduce((a, b) => a + b, 0);
}
add(1, 2); // OK
add(1, 2, 3); // Error
add(1, 2, 3, 4);// OK
keyof
keyof 与 Object.keys 略有相似,只不过 keyof 取 interface 的键。
interface Point {
x: number;
y: number;
}
// type keys = "x" | "y"
type keys = keyof Point;
假设有一个 object 如下所示,我们需要使用 typescript 实现一个 get 函数来获取它的属性值
const data = {
a: 3,
hello: 'world'
}
function get(o: object, name: string) {
return o[name]
}
我们刚开始可能会这么写,不过它有很多缺点
- 无法确认返回类型:这将损失 ts 最大的类型校验功能
- 无法对 key 做约束:可能会犯拼写错误的问题
这时可以使用 keyof 来加强 get 函数的类型功能,有兴趣的同学可以看看 _.get 的 type 标记以及实现
function get<T extends object, K extends keyof T>(o: T, name: K): T[K] {
return o[name]
}
typeof
顾名思义,typeof 代表取某个值的 type,可以从以下示例来展示他们的用法
const a: number = 3
// 相当于: const b: number = 4
const b: typeof a = 4
在一个典型的服务端项目中,我们经常需要把一些工具塞到 context 中,如config,logger,db models, utils 等,此时就使用到 typeof。
import logger from './logger'
import utils from './utils'
interface Context extends KoaContect {
logger: typeof logger,
utils: typeof utils
}
app.use((ctx: Context) => {
ctx.logger.info('hello, world')
// 会报错,因为 logger.ts 中没有暴露此方法,可以最大限度的避免拼写错误
ctx.loger.info('hello, world')
})
in
in 操作符用于遍历目标类型的公开属性名。类似 for .. in 的机制。
使用于枚举类型
enum Letter {
A,
B,
C
}
type LetterMap = {
[key in Letter]: string
};
// interface LetterMap {
// 0: string;
// 1: string;
// 2: string;
// }
使用于联合类型:
type Property = 'name'|'age'|'phone';
type PropertyObject = {
[key in Property]: string
};
// type PropertyObject = {
// name: string;
// age: string;
// phone: string;
// }
使用与基础类型
type StringKey = {
[key in string]: string;
};
// type StringKey = {
// [key: string]: string;
// };
type NumberKey = {
[key in number]: string;
};
// type NumberKey = {
// [key: number]: string;
// };
never
never类型是 Typescript 中的底层类型。代表从不会出现的值。
举个例子:
interface Foo {
type: 'foo';
}
interface Bar {
type: 'bar';
}
type All = Foo|Bar;
function handleValue(val: All) {
switch (val.type) {
case 'foo': {
// 这里 val 被收窄为 Foo
break;
}
case 'bar': {
// 这里 val 被收窄为 Bar
break;
}
default: {
// 这里 val 是 never
const exhaustiveCheck: never = val;
break;
}
}
}
注意在 default 里面我们把被收窄为 never 的 val 赋值给一个显示声明为 never 的变量。
如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事改了 All 的类型:
type All = Foo|Bar|Baz;
如果他忘记了再 handleValue 里面加上针对 Baz 的处理逻辑,这个时候在 default 里面 val 会被收窄为 Baz,
导致无法赋值给 never,产生一个编译错误。
所以通过这个办法,你可以确保 handleValue 总是穷尽了所有 All 的可能类型。
is
使用 is 来判定值的类型
function isArray<T>(val: unknown): val is Array<T> {
// 判断是否数组的实现
return !!val;
}
function doSomething(data: unknown) {
if (isArray(data)) {
// 这里已经被识别为 Array 类型,可以使用数组方法
data.forEach((v) => {
});
} else {
// 这里会报错,无法识别 data 类型
data.forEach((v) => {
});
}
}
Condition Type
类似于 js 中的 ?: 运算符,可以使用它扩展一些基本类型
type isTrue<T> = T extends true ? true : false
// 相当于 type t = false
type t = isTrue<number>
// 相当于 type t = false
type t1 = isTrue<false>
infer
infer 可以用来进行类型推测
type ParamType<T> = T extends (param: infer P) => any ? P : T;
interface User {
name: string;
age: number;
}
type FunC = (user: User) => void;
function log(msg: string) {
console.log(msg);
}
type Param = ParamType<FunC>; // Param = User
type ParamLog = ParamType<typeof log>; // ParamLog = string
infer P 表示待推断的类型变量
整句表示 如果 T 能赋值给 (param: infer P) => any,则结果是 (param: infer P) => any 类型中的参数 P ,否则返回 T。
再举个例子,反解 Promise:
type PromiseType<T> = (...args: any[]) => Promise<T>;
type UnPromisify<T> = T extends PromiseType<infer U> ? U : never;
async function stringPromise() {
return 'string promise';
}
async function numberPromise() {
return 1;
}
type ExtractStringPromise = UnPromisify<typeof stringPromise>; // string
type ExtractNumberPromise = UnPromisify<typeof numberPromise>; // number
readonly
readonly 可以标记属性只读。
interface Foo {
readonly bar: number;
readonly bas: number;
}
const foo: Foo = { bar: 1, bas: 2 };
foo.bas = 444; // TS2540: Cannot assign to 'bas' because it is a read-only property.
内置类型
Partial
作用是将属性变为可选项,定义:
type Partial<T> = {
[P in keyof T]?: T[P];
};
示例:
interface Foo {
name: string;
age: number;
phone: number;
}
type FooPartial = Partial<Foo>;
// type FooPartial = {
// name?: string;
// age?: number;
// phone?: number;
// }
Required
作用是将属性变为必选项,定义:
type Required<T> = {
[P in keyof T]-?: T[P];
};
示例:
interface Foo {
name?: string;
age?: number;
phone?: number;
}
type FooRequired = Required<Foo>;
// type FooPartial = {
// name: string;
// age: number;
// phone: number;
// }
Readonly
作用是将属性变为只读,定义:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
示例:
interface Foo {
name: string;
age: number;
phone: number;
}
type FooReadonly = Readonly<Foo>;
// type FooReadonly = {
// readonly name: string;
// readonly age: number;
// readonly phone: number;
// }
Exclude
作用是从 T 中排除 U,定义:
type Exclude<T, U> = T extends U ? never : T;
示例:
interface Foo1 {
name: string;
phone: number;
}
interface Foo2 {
name: string;
age: number;
}
// 从 name,age 中排除 name,phone
type Ttt = Exclude<keyof Foo2, keyof Foo1>; // age
Extract
作用是从 T 中提取出 U,定义:
type Extract<T, U> = T extends U ? T : never;
示例:
interface Foo1 {
name: string;
phone: number;
}
interface Foo2 {
name: string;
age: number;
}
// 从 name,age 中提取 name,phone
type Ttt = Extract<keyof Foo2, keyof Foo1>; // name
Record
作用是将 K 中的所有属性的值转化为 T 类型,定义:
type Record<K extends keyof any, T> = {
[P in K]: T;
};
示例:
interface Foo {
name: string;
age: number;
phone: number;
}
type Foo1 = Record<keyof Foo, string>;
// interface Foo1 {
// name: string;
// age: string;
// phone: string;
// }
Pick
作用是挑选出一些属性,定义:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
示例:
interface Foo {
name: string;
age: number;
phone: number;
}
type FooPick = Pick<Foo, 'name'>;
// type FooPick = {
// name: string;
// }
Omit
作用是排除一些属性,定义:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
示例:
interface Foo {
name: string;
age: number;
phone: number;
}
type FooOmit = Omit<Foo, 'name'>;
// type FooOmit = {
// age: number;
// phone: number;
// }
NonNullable
作用是排除 null 与 undefined 类型,定义:
type NonNullable<T> = T extends null | undefined ? never : T;
示例:
type T = 'name'|null|undefined;
type Name = NonNullable<T>;
// type Name = 'name';
Parameters
作用是提取函数的参数类型,定义:
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
示例:
function add(a: number, b: string) {
return a + b;
}
type Params = Parameters<typeof add>;
// type Params = [number, string];
ConstructorParameters
作用是提取类构造函数的参数类型,定义:
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
示例:
class Parent {
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
type ConstructorParams = ConstructorParameters<typeof Parent>;
// type ConstructorParams = [string];
ReturnType
作用是提取函数的返回值类型,定义:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
示例:
function add(a: number, b: string) {
return a + b;
}
type AddReturnType = ReturnType<typeof add>;
// type AddReturnType = string;
InstanceType
作用是获取构造函数的实例类型,定义:
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
class Parent {
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
type ParentInstance = InstanceType<typeof Parent>;
// type ParentInstance = Parent;
自定义增强类型
PartialSome
作用是选择一部分变为可选,定义:
type PartialSome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Partial<Pick<T, K>>;
示例:
// 选择一部分变为可选
type PartialSome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Partial<Pick<T, K>>;
interface Foo {
name: string;
age: number;
phone: number;
}
type FooPartialSome = PartialSome<Foo, 'age'>;
// type FooPartialSome = {
// name: string;
// age?: number;
// phone: number;
// }
RequiredSome
作用是选择一部分变为必选,定义:
type RequiredSome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Required<Pick<T, K>>;
示例:
// 选择一部分变为必选
type RequiredSome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Required<Pick<T, K>>;
interface Foo {
name?: string;
age?: number;
phone?: number;
}
type FooRequiredSome = RequiredSome<Foo, 'age'>;
// type FooRequiredSome = {
// name?: string;
// age: number;
// phone?: number;
// }
ReadonlySome
作用是选择一部分变为只读,定义:
type ReadonlySome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Readonly<Pick<T, K>>;
示例:
// 选择一部分变为只读
type ReadonlySome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Readonly<Pick<T, K>>;
interface Foo {
name: string;
age: number;
phone: number;
}
type FooReadonlySome = ReadonlySome<Foo, 'name'>;
// type FooReadonlySome = {
// readonly name: string;
// age: number;
// phone: number;
// }
Mutable
作用是移除属性只读状态,定义:
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
示例:
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
interface Foo {
readonly name: string;
readonly age: number;
readonly phone: number;
}
type FooMutable = Mutable<Foo>;
// type FooMutable = {
// name: string;
// age: number;
// phone: number;
// }
MutableSome
作用是移除一部分属性只读状态,定义:
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
type MutableSome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Mutable<Pick<T, K>>;
示例:
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
type MutableSome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Mutable<Pick<T, K>>;
interface Foo {
readonly name: string;
readonly age: number;
readonly phone: number;
}
type FooMutableSome = MutableSome<Foo, 'phone'>;
// type FooMutable = {
// readonly name: string;
// readonly age: number;
// phone: number;
// }
**