TypeScript专题—深度解析

826 阅读11分钟

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 -s

2、有状态组件开发

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>; // true

8、函数重载

函数重载的意义在于能够让你知道传入不同的参数得到不同的结果,如果传入的参数不同,但是得到的结果类型却相同,那么这里就不要使用函数重载。

函数重载的基本语法:

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): number

9、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;