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
複製代碼
相關文章
相關標籤/搜索