前言

TypeScript 是 JS 类型的超集,并支持了泛型、类型、命名空间、枚举等特性,弥补了 JS 在大型应用开发中的不足。
使用 TypeScript 的编程体验最直观的是当在键盘上敲下 . 时,后面这一大串的提示真的是很方便,代码质量和效率提升十分明显,习惯后真的会使用不惯 JavaScript。
但是 TypeScript 和一些框架结合使用的话坑还是比较多的,例如使用 antd 框架的时候需要去查看框架提供的 .d.ts 的声明文件中一些复杂类型的定义,或者一些没有使用 TypeScript 插件结合起来使用会出现障碍等等。

React

在使用 React 时需要把 React 的 TypeScript 模块的声明包安装一下,否则会在使用 React 时无法找到某些模块而报错。

1
npm i @types/react @types/react-dom -S

使用 class 类名开发组件

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import * as React from 'react'

interface IProps {
color: string,
size?: string,
}
interface IState {
count: number,
}
class App extends React.Component<IProps, IState> {
public state = {
count: 1,
}
public render () {
return (
<div>Hello world</div>
)
}
}

TypeScript 可以对 JSX 进行解析,充分利用其本身的静态检查功能,使用泛型进行 Props、 State 的类型定义。定义后在使用 this.state 和 this.props 时可以在编辑器中获得更好的智能提示,并且会对类型进行检查。
Component 的泛型如下:

1
2
3
4
5
6
7
8
9
class Component<P, S> {

readonly props: Readonly<{ children?: ReactNode }> & Readonly<P>;

state: Readonly<S>;

}
// 只列举了props与state的定义
// Component 这个泛型类, P 代表 Props 的类型, S 代表 State 的类型。

Readonly 泛型把传入的值变为只读,如以上 props 定义的类型都变为只读。
Readonly 实现源码 node_modules/typescript/lib/lib.es5.d.ts ,是 typescript 自带的。

由于 props 属性被设置为只读,所以通过 this.props.size = ‘sm’ 进行更新时候 TS 检查器会进行错误提示

1
Error:(23, 16) TS2540: Cannot assign to 'size' because it is a constant or a read-only property

state 也一样为只读,而 React 的 state 更新需要使用 setState 方法,直接修改 state TS 检查器会进行错误提示。

使用 Function 开发组件

在 React 的声明文件中 已经定义了一个 SFC 类型,使用这个类型可以避免我们重复定义 children、 propTypes、 contextTypes、 defaultProps、displayName 的类型。

使用 SFC 进行无状态组件开发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { SFC } from 'react'
import { MouseEvent } from 'react'
import * as React from 'react'
interface IProps {
onClick (event: MouseEvent<HTMLDivElement>): void,
}
const Button: SFC<IProps> = ({onClick, children}) => {
return (
<div onClick={onClick}>
{ children }
</div>
)
}
export default Button;

事件处理

我们在进行事件注册时经常会在事件处理函数中使用 event 事件对象,比如 input 输入后需要获取它的 value 值,那么就要使用event.target.value,但没有定义 ts 就无法通过 ts 检查,而定义 any 会失去静态检查的意义,还有一个问题就是对 event 定义 any,那么获取 event.target 下的属性也会报错,因为 any 只对 event 的属性定义的,而自已通过 interface 对 event 对象进行类型声明编写的话又十分浪费时间,幸运的是 React 的声明文件提供了 Event 对象的类型声明。

如:

1
2
3
4
5
6
7
8
9
10
11
12
class App extends React.Component {
change = (event: React.ChangeEvent<HTMLInputElement>) => {
console.log(event.target.value);
};
public render() {
return (
<div className="App">
<input type="text" onChange={this.change} />
</div>
);
}
}

Event 事件对象类型

常用 Event 事件对象类型:

  1. ClipboardEvent 剪贴板事件对象

  2. DragEvent 拖拽事件对象

  3. ChangeEvent Change 事件对象

  4. KeyboardEvent 键盘事件对象

  5. MouseEvent 鼠标事件对象

  6. TouchEvent 触摸事件对象

  7. WheelEvent 滚轮事件对象

  8. AnimationEvent 动画事件对象

  9. TransitionEvent 过渡事件对象

实现源码的 node_modules/@types/react/index.d.ts

事件处理函数类型

当我们定义事件处理函数时有没有更方便定义其函数类型的方式呢?答案是使用 React 声明文件所提供的 EventHandler 类型别名,通过不同事件的 EventHandler 的类型别名来定义事件处理函数的类型。

EventHandler 类型实现源码 node_modules/@types/react/index.d.ts 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type EventHandler<E extends SyntheticEvent<any>> = { bivarianceHack(event: E): void }["bivarianceHack"];
type ReactEventHandler<T = Element> = EventHandler<SyntheticEvent<T>>;
type ClipboardEventHandler<T = Element> = EventHandler<ClipboardEvent<T>>;
type DragEventHandler<T = Element> = EventHandler<DragEvent<T>>;
type FocusEventHandler<T = Element> = EventHandler<FocusEvent<T>>;
type FormEventHandler<T = Element> = EventHandler<FormEvent<T>>;
type ChangeEventHandler<T = Element> = EventHandler<ChangeEvent<T>>;
type KeyboardEventHandler<T = Element> = EventHandler<KeyboardEvent<T>>;
type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;
type TouchEventHandler<T = Element> = EventHandler<TouchEvent<T>>;
type PointerEventHandler<T = Element> = EventHandler<PointerEvent<T>>;
type UIEventHandler<T = Element> = EventHandler<UIEvent<T>>;
type WheelEventHandler<T = Element> = EventHandler<WheelEvent<T>>;
type AnimationEventHandler<T = Element> = EventHandler<AnimationEvent<T>>;
type TransitionEventHandler<T = Element> = EventHandler<TransitionEvent<T>>;

EventHandler 接收 E ,其代表事件处理函数中 event 对象的类型。
bivarianceHack 为事件处理函数的类型定义,函数接收一个 event 对象,并且其类型为接收到的泛型变量 E 的类型, 返回值为 void。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface IProps {
onClick: React.MouseEventHandler<HTMLInputElement>;
}

class App extends React.Component<IProps, {}> {

public render() {
return (
<div className="App">
<input
type="text"
onClick={this.props.onClick}
/>
</div>
);
}
}

Promise 类型

在做异步操作时我们经常使用 async 函数,函数调用时会 return 一个 Promise 对象,可以使用 then 方法添加回调函数。

Promise<T> 是一个泛型类型,T 泛型变量用于确定使用 then 方法时接收的第一个回调函数的参数类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface IResponse<T> {
message: string,
result: T,
success: boolean,
}
async function getResponse (): Promise<IResponse<number[]>> {
return {
message: '获取成功',
result: [1, 2, 3],
success: true,
}
}
getResponse()
.then(response => {
console.log(response.result)
})

我们首先声明 IResponse 的泛型接口用于定义 response 的类型,通过 T 泛型变量来确定 result 的类型。

然后声明了一个 异步函数 getResponse 并且将函数返回值的类型定义为 Promise<IResponse<number[]>>

最后调用 getResponse 方法会返回一个 promise 类型,通过 then 调用,此时 then 方法接收的第一个回调函数的参数 response 的类型为,{ message: string, result: number[], success: boolean}

Promise 实现源码 node_modules/typescript/lib/lib.es5.d.ts。

工具与泛型的使用技巧

ts 提供的一些实用的泛型。
实现源码 node_modules/typescript/lib/lib.es5.d.ts

typeof

一般我们都是先定义类型,再去赋值使用,但是使用 typeof 我们可以把使用顺序倒过来。

1
2
3
4
5
6
const options = {
result: {
abc:"xxx"
}
}
type Options = typeof options

使用字面量类型限制值为固定的参数

如限制 props.pick 的值只可以是字符串 a、b、c ,不一定是字符串,当然也可以是数字 number。

1
2
3
interface IProps {
pick: 'a' | 'b' | 'c',
}

使用 Partial 将所有的 props 属性都变为可选值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface IProps {
pick: string;
mouu: string;
abc: number;
}
let p:Partial<IProps>;

等价于==>

interface IProps {
pick?: string;
mouu?: string;
abc?: number;
}
let p:IProps;

源码:

1
2
3
type Partial<T> = { [P in keyof T]?: T[P] };

上面代码的意思是 keyof T 拿到 T 所有属性名, 然后 in 进行遍历, 将值赋给 P , 最后 T[P] 取得相应属性的值,中间的 ? 用来进行设置为可选值。

使用 Required 将所有 props 属性都设为必填项

效果与 Partial 相反
源码

1
2
3
type Required<T> = { [P in keyof T]-?: T[P] };

-? 的功能就是把可选属性的 ? 去掉使该属性变成必选项,对应的还有 +? ,作用与 -? 相反,是把属性变为可选项。

条件类型

条件类型可以根据其他类型的特性做出类型的判断。

1
T extends U ? X : Y

如传入 T 与 U,当 T 中的内容在 U 中,那么就 true,返回 X,否则返回 U。

如:

1
2
3
4
5
6
7
8
9
10
// 之前条件的判断方法
interface Id { id: number}
interface Name { name: string }
declare function createLabel(id: number): Id;
declare function createLabel(name: string): Name;
declare function createLabel(name: string | number): Id | Name;

// 使用条件类型
type IdOrName<T extends number | string> = T extends number ? Id : Name;
declare function createLabel<T extends number | string>(idOrName: T): T extends number ? Id : Name;

它是包含关系,如果包含怎么处理,不包含怎么处理,一般如下使用

1
2
3
4
5
6
7
8
9
type aa = "a" | "b" | "c" | "d";
type bb = "a" | "c" | "f";

type Inter<T,U> = T extends U ? T : never;


let ceshi:Inter<bb,aa>;
等价于===
let ceshi:"b" | "d";
  1. Exclude<T,U>
    从 T 中排除那些可以赋值给 U 的类型。
1
type T = Exclude<1|2|3|4|5, 3|4>  // T = 1|2|5
  1. Extract<T,U>
    从 T 中提取那些可以赋值给 U 的类型。
1
type T = Extract<1|2|3|4|5, 3|4>  // T = 3|4
  1. Pick<T,K>
    从 T 中取出一系列 K 的属性。
1
2
3
4
5
6
7
8
9
interface Person {
name: string;
age: number;
sex: string;
}
let person: Pick<Person, 'name' | 'age'> = { // {name: string;age: number;}
name: '小王',
age: 21,
}
  1. Record<K,T>
    将 K 中所有的属性的值转化为 T 类型。
1
2
3
4
let person: Record<'name' | 'age', string> = { // {name: string;age: string;}
name: '小王',
age: '12',
}
  1. Omit<T,K>
    从对象 T 中排除 key 是 K 的属性。
1
2
3
4
5
6
7
8
9
10
11
 interface Person {
name: string,
age: number,
sex: string,
}

// 排除 name 属性。
let person: Omit<Person, 'name'> = {
age: 1,
sex: '男'
}
  1. NonNullable
    排除 T 为 null 、undefined。
1
type T = NonNullable<string | string[] | null | undefined>; // string | string[]
  1. ReturnType
    获取函数 T 返回值的类型。
1
2
3
4
5
6
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;

// infer R 相当于声明一个变量,接收传入函数的返回值类型。

type T1 = ReturnType<() => string>; // string
type T2 = ReturnType<(s: string) => void>; // void

ts 官网

高阶组件

由于使用了高阶组件是把原来的组件包一层然后返回,但对于使用了 typescript 后传入到组件里的值经过包的那一层是没有组件的传入值的定义的,所以会报错,
那么我们就要对其进行处理。
这里我们使用泛型:P 表示传递到 HOC 的组件的 props。React.ComponentType

是 React.FunctionComponent

| React.ClassComponent

的别名,表示传递到 HOC 的组件可以是类组件或者是函数组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
import * as React from "react";

export const useInfo = <P extends object>(
Component: React.ComponentType<P>
) => {
// ...TODO
const useTest: React.SFC<P & {}> = props => {
// ...TODO
return <Component {...props} />;
};

return useTest;
};