TypeScript在React中使用总结

编写第一个TSX组件

import React from 'react'
import ReactDOM from 'react-dom'

const App = () => {
  return (
    <div>Hello World</div>
  )
}

ReactDOM.render(<App/>, document.getElementById('root'))
复制代码

上述代码运行时会出现如下错误node

  • Cannot find module 'react'
  • Cannot find module 'react-dom'

错误缘由是因为ReactReact-dom并非使用TS 进行开发的,因此 TS 不知道 React、 React-dom 的类型,以及该模块导出了什么,此时须要引入 .d.ts 的声明文件,比较幸运的是在社区中已经发布了这些经常使用模块的声明文件 DefinitelyTypedreact

安装 ReactReact-dom 类型定义文件

使用yarn安装git

yarn add @types/react 
yarn add @types/react-dom 
复制代码

使用npm安装github

npm i @types/react -s
npm i @types/react-dom -s
复制代码

有状态组件开发

咱们定义一个 App 有状态组件,propsstate 以下typescript

props

props 类型 是否必传
color string
size string

state

state 类型
count string

使用TSX咱们能够这样写shell

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 的类型定义。npm

那么 Component 的泛型是如何实现的呢,咱们能够参考下 React 的类型定义文件 node_modules/@types/react/index.d.tspromise

P 表明 Props 的类型,S 表明 State 的类型dom

class Component<P, S> {
  readonly props: Readonly<{ children?: ReactNode }> & Reactonly<P>
  state: Reactonly<S>
}
复制代码

Component泛型类在接收到 PS 这两个范型变量后,将只读属性 props 的类型声明为交叉类型 readonly props: Readonly<{ children?: ReactNode }> & Reactonly<P> 使其支持 children 以及咱们声明的 colorsize异步

经过范型的类型别名 Readonlyprops 的全部属性都设置为只读属性。

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

因为 props 属性被设置为只读,因此经过 this.props.size = 'sm' 进行更新时候 TS 检查器会进行错误提示,Error:(23, 16) TS2540: Cannot assign to 'size' because it is a constant or a read-only property

防止直接更新 state

React的 state 更新须要使用 setState 方法,可是咱们常常误操做,直接对 state 的属性进行更新。

this.state.count = 2
复制代码

咱们能够经过将 state,以及 state 下面的属性都设置为只读属性,从而防止直接更新 state

import * as React from 'react'

interface IProps {
  color: string,
  size?: string,
}
interface IState {
  count: number,
}

class App extends React.PureComponent<IProps, IState> {
  public readonly state: Readonly<IState> = {
    count: 1,
  }
  public render () {
    return (
      <div>Hello world</div>
    )
  }
  public 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.

无状态组件开发

Props

props 类型 是否必传
children ReactNode
onClick function

FC 类型

在React声明文件中,已经定义了一个FC类型,使用这个类型能够避免咱们重复定义 propTypescontextTypesdefaultPropsdisplayName 的类型。

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

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
复制代码

事件处理

咱们在进行事件注册时常常会在事件处理函数中使用 event 事件对象,例如当使用鼠标事件时咱们经过 clientXclientY 去获取指针的坐标。

你们能够想到直接把 event 设置为 any 类型,可是这样就失去了咱们对代码进行静态检查的意义。

function handleEvent (event: any) {
  console.log(event.clientY)
}
复制代码

试想下当咱们注册一个 Touch 事件,而后错误的经过事件处理函数中的 event 对象去获取其 clientY 属性的值,在这里咱们已经将 event 设置为 any 类型,致使 TypeScript 在编译时并不会提示咱们错误, 当咱们经过 event.clientY 访问时就有问题了,由于 Touch 事件的 event 对象并无 clientY 这个属性。

经过 interfaceevent 对象进行类型声明编写的话又十分浪费时间,幸运的是 React 的声明文件提供了 Event 对象的类型声明。

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,
}
复制代码

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

interface SyntheticEvent<T = Element> {
        bubbles: boolean;
        /**
         * A reference to the element on which the event listener is registered.
         */
        currentTarget: EventTarget & T;
        cancelable: boolean;
        defaultPrevented: boolean;
        eventPhase: number;
        isTrusted: boolean;
        nativeEvent: Event;
        preventDefault(): void;
        isDefaultPrevented(): boolean;
        stopPropagation(): void;
        isPropagationStopped(): boolean;
        persist(): void;
        // If you thought this should be `EventTarget & T`, see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/12239
        /**
         * A reference to the element from which the event was originally dispatched.
         * This might be a child element to the element on which the event listener is registered.
         *
         * @see currentTarget
         */
        target: EventTarget;
        timeStamp: number;
        type: string;
}

interface MouseEvent<T = Element> extends SyntheticEvent<T> {
        altKey: boolean;
        button: number;
        buttons: number;
        clientX: number;
        clientY: number;
        ctrlKey: boolean;
        /**
         * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method.
         */
        getModifierState(key: string): boolean;
        metaKey: boolean;
        nativeEvent: NativeMouseEvent;
        pageX: number;
        pageY: number;
        relatedTarget: EventTarget;
        screenX: number;
        screenY: number;
        shiftKey: boolean;
    }
复制代码

EventTarget 类型实现源码 node_modules/typescript/lib/lib.dom.d.ts

interface EventTarget {
    addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
    dispatchEvent(evt: Event): boolean;
    removeEventListener(type: string, listener?: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
}
复制代码

经过源码咱们能够看到 MouseEvent<T = Element> 继承 SyntheticEvent<T>,而且经过 T 接收一个 DOM 元素的类型, currentTarget 的类型由 EventTarget & T 组成交叉类型。

事件处理函数类型

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

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

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

实例:

interface IProps {
  onClick: MouseEventHandler<HTMLDivElement>
}
复制代码

Promise 类型

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

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)
  })
复制代码

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

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

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

Promise<T> 实现源码 node_modules/typescript/lib/lib.es5.d.ts

interface Promise<T> {
    /** * Attaches callbacks for the resolution and/or rejection of the Promise. * @param onfulfilled The callback to execute when the Promise is resolved. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of which ever callback is executed. */
    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>; /** * Attaches a callback for only the rejection of the Promise. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of the callback. */ catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>; } 复制代码

可索引类型

实例:

interface StringArray { 
 [index: number]: string
}

let myArray: StringArray
myArray = ["Bob", "Fred"]

let myStr: string = myArray[0]
复制代码

上面例子里,咱们定义了 StringArray 接口,它具备索引签名。 这个索引签名表示了当用 number 去索引 StringArray 时会获得 string 类型的返回值。

Typescript 支持两种索引签名:字符串和数字。 能够同时使用两种类型的索引,可是数字索引的返回值必须是字符串索引返回值类型的子类型。

是由于当使用 number 来索引时,JavaScript 会将它转换成 string 而后再去索引对象。 也就是说用 100(一个number)去索引等同于使用 "100"(一个string)去索引,所以二者须要保持一致。

class Animal { 
  name: string
}
class Dog extends Animal {  
  breed: string
}

// 错误:使用数值型的字符串索引,有时会获得彻底不一样的Animal
interface NotOkay { 
  [x: number]: Animal  
  [x: string]: Dog
}
复制代码

下面的例子里,name 的类型与字符串索引类型不匹配,因此类型检查器给出一个错误提示

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!
复制代码

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
复制代码

函数重载

函数重载的基本语法:

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
复制代码

这里咱们申明了两次?!为何我不能判断类型或者可选参数呢?后来我遇到这么一个场景:

interface User {
  name: string
  age: number
}

declare function test(para: User | number, flag?: boolean): number 复制代码

在这个 test 函数里,咱们的本意多是当传入参数 para 是 User 时,不传 flag,当传入 para 是 number 时,传入 flag。TypeScript 并不知道这些,当你传入 para 为 User 时,flag 一样容许你传入:

const user = {
  name: 'Jack',
  age: 666
}

// 没有报错,可是与想法违背
const res = test(user, false);
复制代码

使用函数重载能帮助咱们实现:

interface User {
  name: string
  age: number
}

declare function test(para: User): number declare function test(para: number, flag: boolean): number const user = { name: 'Jack', age: 666 };

// bingo
// Error: 参数不匹配
const res = test(user, false)
复制代码

实际项目中,你可能要多写几步,如在 class 中:

interface User {
  name: string
  age: number
}

const user = {
  name: 'Jack',
  age: 123
};

class SomeClass {
  /** * 注释 1 */
  public test(para: User): number
  /** * 注释 2 */
  public test(para: number, flag: boolean): number
  public test(para: User | number, flag?: boolean): number {
    // 具体实现
    return 11
  }
}

const someClass = new SomeClass()

// ok
someClass.test(user)
someClass.test(123, false)

// Error
someClass.test(123)
someClass.test(user, false)
复制代码

函数重载的意义在于可以让你知道传入不一样的参数获得不一样的结果,若是传入的参数不一样,可是获得的结果(类型)却相同,那么这里就不要使用函数重载(没有意义)

若是函数的返回值类型相同,那么就不须要使用函数重载。

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 复制代码

工具泛型使用技巧

typeof

通常咱们都是先定义类型,再去赋值使用,可是使用 typeof 咱们能够把使用顺序倒过来。

const options = {
  a: 1
}

type Options = typeof options
复制代码

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

限制 props.color 的值只能够是字符串 redblueyellow

interface IProps {
  color: 'red' | 'blue' | 'yellow',
}
复制代码

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

限制 props.index 的值只能够是数字 012

interface IProps {
 index: 0 | 1 | 2,
}
复制代码

使用 Partial 将全部的 props 属性都变为可选值

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

type Partial<T> = { [P in keyof T]?: T[P] };
复制代码

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

若是 props 全部的属性值都是可选的咱们能够借助 Partial 这样实现。

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>
  )
复制代码

使用 Required 将全部 props 属性都设为必填项

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

type Required<T> = { [P in keyof T]-?: T[P] };
复制代码

看到这里,小伙伴们可能有些疑惑, -? 是作什么的,其实 -? 的功能就是把可选属性的 ? 去掉使该属性变成必选项,对应的还有 +? ,做用与 -? 相反,是把属性变为可选项。

条件类型

TypeScript2.8引入了条件类型,条件类型能够根据其余类型的特性作出类型判断。

T extends U ? X : Y
复制代码

原先

interface Id { id: number, /* other fields */ }
interface Name { name: string, /* other fields */ }
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;
复制代码

Exclude<T,U>

T 中排除那些能够赋值给 U 的类型

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

type Exculde<T,U> = T extends U ? never : T;
复制代码

实例:

type T = Exclude<1|2|3|4|5, 3|4>  // T = 1|2|5 
复制代码

此时 T 类型的值只能够为 123 ,当使用其余值是 TS 会进行错误提示。

Error:(8, 5) TS2322: Type '3' is not assignable to type '1 | 2 | 5'.

Extract<T,U>

T 中提取那些能够赋值给 u 的类型。

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

type Extract<T, U> = T extends U ? T : never;
复制代码

实例:

type T = Extract<1|2|3|4|5, 3|4>  // T = 3|4;
复制代码

此时 T 类型的值只能够为 34 ,当使用其余值时 TS 会进行错误提示:

Error:(8, 5) TS2322: Type '5' is not assignable to type '3 | 4'.

Pick<T,K>

T 中取出一系列 K 的属性。

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

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
复制代码

实例:

假如咱们如今有一个类型其拥有 nameagesex 属性,当咱们想生成一个新的类型只支持 nameage 时能够像下面这样:

interface Person {
  name: string,
  age: number,
  sex: string,
}
let person: Pick<Person, 'name' | 'age'> = {
  name: '小王',
  age: 21,
}
复制代码

Record<K,T>

k 中全部的属性的值转化为 T 类型。

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

type Record<K extends keyof any, T> = {
    [P in K]: T;
};
复制代码

实例:

nameage 属性所有设为 string 类型。

let person: Record<'name' | 'age', string> = {
  name: '小王',
  age: '12',
}
复制代码

Omit<T,K>(没有内置)

从对象 T 中排除 keyK 的属性。

因为 TS 中没有内置,因此须要咱们使用 PickExclude 进行实现。

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
复制代码

实例:

排除 name 属性。

interface Person {
  name: string,
  age: number,
  sex: string,
}

let person: Omit<Person, 'name'> = {
  age: 1,
  sex: '男'
}
复制代码

NonNullable

排除 Tnullundefined

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

type NonNullable<T> = T extends null | undefined ? never : T;
复制代码

实例:

type T = NonNullable<string | string[] | null | undefined>; // string | string[]
复制代码

ReturnType

获取函数 T 返回值的类型

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

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
复制代码
相关文章
相关标签/搜索