TypeScript在React高階元件中的使用技巧

微匯金融大前端發表於2018-02-23

元件引數的編譯期檢查

隨著TypeScript語言特性的完善,我們團隊的前端程式碼已完全遷移至TypeScript。當前TypeScript完全支援JSX語法,舉個例子:

import * as React from 'react'

type Props = {
  p1: string
}

class ComponentA extends React.Component<Props> {
  render(){
    return <div>{this.props.p1}</div>
  }
}

<ComponentA /> //編譯時錯誤,p1必須傳入
<ComponentA p1="test"/> //正確
複製程式碼

這樣我們宣告的ComponentA元件就可以在編譯時檢查傳入引數是否正確。

如果我們希望元件的某個引數是選填的,只需要在引數定義前加一個問號即可:

import * as React from 'react'

type Props = {
  p1?: string
}

class ComponentA extends React.Component<Props> {
  render(){
    return <div>{this.props.p1 || "empty"}</div>
  }
}

<ComponentA /> //正確,p1為非必需引數
<ComponentA p1="test"/> //正確
複製程式碼

高階元件

在使用React的過程中,我們不可避免的會對現有元件進行再封裝,形成很多高階元件。

對於這種情況,我們首先想到的方案是編寫獨立的高階元件,如:

import * as React from 'react'

type BCProps = {
  p1: string
}

class BC extends React.Component<BCProps> {
  render(){
    return <div>{this.props.p1}</div>
  }
}

type HOCProps = {
  p1h:string
}

class HOC extends React.Component<HOCProps> {
  render(){
    return <BC p1={this.props.p1h}/>
  }
}
複製程式碼

在上面的例子中,BC是基礎元件,HOC是我們封裝的高階元件。我們可以看到BCProps與HOCProps是相互獨立的,這樣做的好處有以下兩點:

  1. 適用性廣,任何場景都可使用此種封裝方式。
  2. 完全解耦,使用者只需要瞭解HOCProps,對BCProps無需關心。

但相對的,其缺點也非常明顯,由於對BC進行了完全封裝,當BCProps改變時,如p1更改了名稱,改為了p1b,此時我們需要更改HOC的程式碼,否則執行會報錯。

舉一個很常見的例子,一個基礎元件自帶樣式並不符合我們的需求,我們希望能封裝一個自定義樣式的高階元件,而其他Props保持不變。這樣的話,在樣式引數的定義不變而其他引數變動的情況下,可以做到高階元件的程式碼無需進行修改。

在JS中,實現這種引數傳遞非常的簡單,但是在TS中會有些麻煩。最笨的方法是我們手動將BCProps中的樣式無關引數複製一份,但是這樣的話如果BCProps被修改,那麼我們需要手動修改HOCProps,無法做到非樣式引數改變,程式碼無需修改的目的。

在當前的TS版本中,我們無法直接對已存在型別的屬性進行刪除操作,如果要實現這種高階元件,首先我們需要定義兩個幫助型別:

type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T];

type Omit<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
複製程式碼

這樣,我們就可以實現不含樣式引數的高階元件:

import * as React from 'react'

type BCProps = {
  p1: string,
  style: React.CSSProperties
}

class BC extends React.Component<BCProps> {
  render(){
    return <div style={this.props.style}>{this.props.p1}</div>
  }
}

type HOCProps = Omit<BCProps, "style">

class HOC extends React.Component<HOCProps> {
  render(){
    return <BC {...Object.assign({},this.props,{style:{color:"red"}})}/>
  }
}
複製程式碼

只要style引數的使用方法不變,當BC被修改時,HOC無需任何改變。


相關文章