1、TypeScript在React中使用需要安装React、React-dom类型定义文件
React和React-dom并不是使用TS进行开发的,所以TS 知道React、React-dom的类型,以及该模块导出了什么,此时需要引入.d.ts的声明文件。
npm i @types/react -s
npm i @types/react-dom -s2、有状态组件开发
import * as React from 'react';
interface IProps {
color: string;
size?: string;
}
interface IState {
count: number;
}
class App extends React.Component<IProps, IState> {
state = {
count: 1,
}
render () {
return (
<div>Hello world</div>
)
}
}Component的泛型实现:
class Component<P, S> {
readonly props: Readonly<{ children?: ReactNode }> & Reactonly<P>
state: Reactonly<S>
}防止直接更新state,如this.state.count = 2:
import * as React from 'react';
interface IProps {
color: string;
size?: string;
}
interface IState {
count:
number;
}
class App extends React.PureComponent<IProps, IState> {
readonly state: Readonly<IState> = {
count: 1,
}
render () {
return (
<div>Hello world</div>
)
}
componentDidMount () {
this.state.count = 2
}
}
export default App;此时直接修改state值的时候TypeScript会立刻告诉我们错误,Error:(23, 16) TS2540: Cannot assign to 'count' because it is a constant or a read-only property.
3、无状态组件开发
在React声明文件中,已经定义了一个FC类型,使用这个类型可以避免我们重复定义 propTypes、contextTypes、defaultProps、displayName 的类型。
FC类型实现:
type FC<P = {}> = FunctionComponent<P>;
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}使用FC进行无状态组件开发:
import * as React from 'react';
import { MouseEvent } from 'react';
interface IProps {
children?: React.ReactNode;
onClick (event: MouseEvent<HTMLDivElement>): void;
}
const Button: React.FC<Iprops> = ({onClick, children}) => {
return (
<div onClick={onClick}>
{ children }
</div>
)
}
export default Button;4、事件处理
我们在进行事件注册时经常会在事件处理函数中使用event事件对象,例如当使用鼠标事件时我们通过clientX、clientY去获取指针的坐标。大家可能会直接把event设置为any类型,但是这样就失去了我们对代码进行静态检查的意义,例如当注册一个Touch事件,然后错误的通过事件处理函数中的event对象去获取其clientY属性的值,在这里我们已经将event 设置为any类型,导致TypeScript在编译时并不会提示我们错误,当我们通过event.clientY 访问时就有问题了,因为Touch事件的event对象并没有clientY这个属性。
Event事件对象类型:
ClipboardEvent<T = Element> 剪切板事件对象
DragEvent<T =Element> 拖拽事件对象
ChangeEvent<T = Element> Change事件对象
KeyboardEvent<T = Element> 键盘事件对象
MouseEvent<T = Element> 鼠标事件对象
TouchEvent<T = Element> 触摸事件对象
WheelEvent<T = Element> 滚轮时间对象
AnimationEvent<T = Element> 动画事件对象
TransitionEvent<T = Element> 过渡事件对象示例:
import { MouseEvent } from 'react';
interface IProps {
onClick (event: MouseEvent<HTMLDivElement>): void;
}事件处理函数类型:
type EventHandler<E extends SyntheticEvent<any>> = { bivarianceHack(event: E): void }["bivarianceHack"]; // 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>>;示例:
interface IProps {
onClick: MouseEventHandler<HTMLDivElement>;
}5、Promise类型
Promise<T>是一个泛型类型,T泛型变量用于确定then方法时接收的第一个回调函数onfulfilled的参数类型。
示例:
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)
})Promise<T>实现源码:
interface Promise<T> {
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}6、可索引类型
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];上述定义了StringArray接口,它具有索引签名,当用number去索引StringArray时会得到 string 类型的返回值。
Typescript支持两种索引签名,即字符串和数字,可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型,因为当使用number来索引时,JavaScript会将它转换成string然后再去索引对象。
示例:
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
// 错误:使用数值型的字符串索引,有时会得到完全不同的Animal
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}
interface NumberDictionary {
[index: string]: number;
length: number; // 可以,length是number类型
name: string; // 错误,`name`的类型与索引类型返回值的类型不匹配
}可以将索引签名设置为只读,这样就可以防止给索引赋值:
interface ReadonlyStringArray {
readonly [index: number]: string
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory" // error!7、extends
extends即为扩展、继承,在ts中,extends关键字既可以来扩展已有的类型,也可以对类型进行条件限定。在扩展已有类型时,不可以进行类型冲突的覆盖操作。例如,A为string,在扩展出的类型中无法将其改为number。
示例:
type num = {
num: number;
}
interface IStrNum extends num {
str: string;
}
// 与上面等价
type TStrNum = A & {
str: string;
}在ts中,可以通过条件类型进行一些三目操作:T extends U ? X : Y
type IsEqualType<A , B> = A extends B ? (B extends A ? true : false) : false;
type NumberEqualsToString = IsEqualType<number,string>; // false
type NumberEqualsToNumber = IsEqualType<number,number>; // true8、函数重载
函数重载的意义在于能够让你知道传入不同的参数得到不同的结果,如果传入的参数不同,但是得到的结果类型却相同,那么这里就不要使用函数重载。
函数重载的基本语法:
declare function test(a: number): number
declare function test(a: string): string
const resS = test('Hello World'); // resS被推断出类型为string
const resN = test(1234); // resN被推断出类型为number只是参数个数的区别可以使用可选参数来代替:
function func (a: number): number
function func (a: number, b: number): number
// 等价于
function func (a: number, b?: number): number只是参数类型的区别可以使用联合类型来代替:
function func (a: number): number
function func (a: string): number
// 等价于
function func (a: number | string): number9、keyof
keyof与Object.keys略有相似,只不过keyof取interface的键。
示例:
interface Point {
x: number;
y: number;
}
type keys = keyof Point; // type keys = "x" | "y"假设有一个object,我们需要使用typescript实现一个get函数来获取它的属性值。
示例:
const data = {
a: 3,
hello: 'world'
};
function get(o: object, name: string) {
return o[name]
}我们刚开始可能会这么写,不过它有很多缺点:
【1】无法确认返回类型,这将损失ts最大的类型校验功能
【2】无法对key做约束,可能会犯拼写错误的问题
这时可以使用keyof来加强get函数的类型功能:
function get<T extends object, K extends keyof T>(o: T, name: K): T[K] {
return o[name];
}10、工具泛型使用技巧
【1】typeof
一般我们都是先定义类型,再去赋值使用,但是使用typeof我们可以把使用顺序倒过来。
示例:
const options = {
a: 1
};
type Options = typeof options;【2】字面量类型
使用字符串字面量类型,限制值为固定的字符串参数:
interface IProps {
color: 'red' | 'blue' | 'yellow';
}使用数字字面量类型限制值为固定的数值参数:
interface IProps {
index: 0 | 1 | 2;
}【3】Readonly<T>
将T中所有属性设置为只读。
Readonly实现源码:
type Readonly<T> = { readonly [P in keyof T]: T[P]; };【4】Partial<T>
使用Partial将所有的props属性都变为可选值。
Partial实现源码:
type Partial<T> = { [P in keyof T]?: T[P] };示例:
import { MouseEvent } from 'react';
import * as React from 'react';
interface IProps {
children: React.ReactNode;
color: 'red' | 'blue' | 'yellow';
onClick (event: MouseEvent<HTMLDivElement>): void;
}
const Button: React.FC<Partial<IProps>> = ({onClick, children, color}) => {
return (
<div onClick={onClick}>
{ children }
</div>
)
}【5】Required<T>
使用Required将所有props属性都设为必填项。
Required实现源码:
type Required<T> = { [P in keyof T]-?: T[P] };-?的功能就是把可选属性的?去掉使该属性变成必选项; +?的功能就是把属性变为可选项。
【6】条件类型
条件类型类似于js中的 ?: 运算符,可以使用它扩展一些基本类型,可以根据其他类型的特性做出类型判断。
基本语法:T extends U ? X : Y
示例:
type isTrue<T> = T extends true ? true : false
type t = isTrue<number> // 相当于 type t = false
type t1 = isTrue<false> // 相当于 type t = false
interface Id { id: number, /* other fields*/ }
interface Name { name: string, /* otherfields */ }
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;【7】Exclude<T, U>
从T中排除那些可以赋值给U的类型。
Exclude实现源码:
type Exculde<T,U> = T extends U ? never : T;示例:
type T = Exclude<1|2|3|4|5, 3|4> // T = 1|2|5【8】Extract<T, U>
从T中提取那些可以赋值给u的类型。
Extract实现源码:
type Extract<T, U> = T extends U ? T : never;示例:
type T = Extract<1|2|3|4|5, 3|4> // T = 3|4;【9】Pick<T, K>
从T中取出一系列K的属性。
Pick实现源码:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};示例:
interface Person {
name: string;
age: number;
sex: string;
}
let person: Pick<Person, 'name' | 'age'> = {
name: '小王',
age: 21,
}【10】Omit<T, K>(没有内置)
从对象T中排除key是K的属性。
Omit实现源码:
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>示例:
interface Person {
name: string;
age: number;
sex: string;
}
let person: Omit<Person, 'name'> = {
age: 1,
sex: '男'
}【11】Record<K, T>
将k中所有的属性的值转化为T类型。
Record实现源码:
type Record<K extends keyof any, T> = {
[P in K]: T;
};示例:
let person: Record<'name' | 'age', string> = {
name: '小王',
age: '12',
}【12】NonNullable<T>
排除T为null、undefined。
NonNullable实现源码:
type NonNullable<T> = T extends null | undefined ? never : T;示例:
type T = NonNullable<string | string[] | null | undefined>; // string | string[]【13】Parameters<T>
返回类型为T的函数的参数类型所组成的数组。
Parameters实现源码:
type Parameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;示例:
declare function f1(arg: { a: number, b: string }): void
type T0 = Parameters<() => string>; // []
type T1 = Parameters<(s: string) => void>; // [string]
type T2 = Parameters<(<T>(arg: T) => T)>; // [unknown]
type T4 = Parameters<typeof f1>; // [{ a: number, b: string }]
type T5 = Parameters<any>; // unknown[]
type T6 = Parameters<never>; // never
type T7 = Parameters<string>; // Error: 类型“string”不满足约束“(...args: any) => any”
type T8 = Parameters<Function>; // Error: 类型“Function”不满足约束“(...args: any) => any”【14】ConstructorParameters<T>
返回类型为构造函数T的参数类型所组成的数组.
ConstructorParameters实现源码:
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;示例:
class Person {
constructor(name: string, age: number) {}
}
type T0 = ConstructorParameters<typeof Person>; // [string, number]【15】ReturnType
获取函数T返回值的类型。
ReturnType实现源码:
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【16】InstanceType<T>
返回构造函数类型T的实例类型。
InstanceType实现源码:
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;示例:
class C {
x = 0;
y = 0;
}
type T0 = InstanceType<typeof C>; // C
type T1 = InstanceType<any>; // any
type T2 = InstanceType<never>; // any
type T3 = InstanceType<string>; // error:类型“string”不满足约束“new (...args: any) => any”
type T4 = InstanceType<Function>; // error:类型“Function”不满足约束“new (...args: any) => any”【17】ThisParameterType<T>
返回函数中this参数类型,需要开启strictFunctionTypes才能工作。
ThisParameterType实现源码:
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;示例:
function toHex(this: Number) {
return this.toString(16);
}
type T0 = ThisParameterType<typeof toHex>; // Number【18】OmitThisParameter<T>
用于删除函数中的this参数,需要开启strictFunctionTypes才能工作。
OmitThisParameter实现源码:
type OmitThisParameter<T> = unknown extends ThisParameterType<T>
? T
: T extends (...args: infer A) => infer R
? (...args: A) => R
: T;示例:
function toHex(this: Number, others: any) {
return this.toString(16);
}
type T1 = OmitThisParameter<typeof toHex>; // (others: any) => string【19】Dictionary
Dictionary类似于es6中的Map,但TypeScript中不能直接用Map,因为TypeScript的target是es5,我们可以使用索引类型模拟。
const dic: { [key: string]: number; } = {
// key为string,value为number;
key1: 1,
};
// 添加key、value
dic['b'] = 2;
dic.c = 3;
// 遍历;
for (const key in dic) {
if (dic.hasOwnProperty(key)) {
console.log(dic[key]);
}
}11、is
先看一个koa的错误处理流程,以下是对error进行集中处理,并且标识code的过程:
app.use(async (ctx, next) => {
try{
await next();
} catch (err) {
let code = 'BAD_REQUEST';
if (err.isAxiosError) {
code = `Axios-${err.code}`
} else if (err instanceof
Sequelize.BaseError) {
}
ctx.body = {
code
}
}
})在err.code处,会编译出错,提示Property 'code' does not exist on type 'Error'.ts(2339)。
此时可以使用as AxiosError或者as any来避免报错:
if ((err as AxiosError).isAxiosError) {
code = `Axios-${(err as AxiosError).code}`;
}此时可以使用is来判定值的类型:
function isAxiosError (error: any): error is AxiosError {
return error.isAxiosError;
}
if (isAxiosError(err)) {
code = `Axios-${err.code};`
}其他示例:
export function isType(type: any): type is GraphQLType;
export function isScalarType(type: any): type is GraphQLScalarType;
export function isObjectType(type: any): type is GraphQLObjectType;
export function isInterfaceType(type: any): type is GraphQLInterfaceType;