【譯】TypeScript筆記:React和泛型

原文: Notes on TypeScript: React and Generics
做者:A. Sharif
譯者:博軒

介紹

這些筆記應該有助於更好的理解 TypeScript,而且在查找如何在特定狀況下使用 TypeScript 會頗有幫助。全部示例都是基於 TypeScript 3.2 完成。react

泛型

若是您一直在閱讀「TypeScript 筆記」系列,那麼到目前爲止您將看到泛型的普遍使用。雖然咱們一直在使用泛型,可是咱們並無真正的介紹泛型,以及它有什麼用。在本系列的這一部分中,咱們將首先嚐試更好的理解泛型的概念,而後瞭解如何將泛型和 React 和 TypeScript 結合在一塊兒更好的工做。typescript

編寫軟件時,一方面是咱們但願某些功能能夠重用,而無需爲每種可能的輸入類型編寫特定的功能。咱們如下面這個例子做爲起點:數組

function isDefinedNumber(a: number) : boolean {
  return a !== null || a !== undefined;
}

function isDefinedString(a: string) : boolean {
  return a!== null || a !== undefined;
}

咱們不會編寫顯式的函數,使用 stringnumber 做爲輸入,而是編寫一個帶有一下簽名的函數:函數

function isDefined<Type>(a: Type) : boolean {
  return a!== null || a !== undefined;
}

isDefined指望輸入泛型 Type。TypeScript 將嘗試推斷參數,並指定一個正確的類型。讓咱們接着看看下一個例子,推斷一下返回的類型:this

function of<Type>(a: Type) : Type[] {
  return [a];
}

const toNumbers = of(1); // const toNumbers: number[]
const toStrings = of("Test Of"); // const toString: string[]

of 示例中,咱們能夠看到咱們甚至不須要定義類型,由於 TypeScript 能夠推斷出參數類型。這並不是適用於全部狀況,有時候,咱們必須明確說明類型。咱們也能夠這樣定義上面的函數:翻譯

const toNumbers = of<number>(1); // const toNumbers: number[]
const toStrings = of<string>("Test Of"); // const toString: string[]

從技術上講,咱們可使用 anycode

function of(a: any) : any {
  if (a.length !== undefined) {
    return a
  }
  return a;
}

可是使用any和泛型之間會存在很大的差別。若是你仔細看看上面的例子,咱們對輸入的參數一無所知。of 使用 undefined 或者 null 值調用將致使錯誤。泛型能夠推斷出確切的類型,並強制在函數體內相應地處理輸入。下面是使用泛型的相同示例:繼承

function of<Type>(a: Type) : Type[] {
  if (a.length !== undefined) { // error: Property 'length' does not exist on 'Type'
    return a
  }
  return [a];
}

在處理泛型時咱們必須更加明確,能夠將示例重寫爲如下內容:ip

function of<Type>(a: Type | Type[]) : Type[] {
  if (Array.isArray(a)) {
    return a
  }
  return [a];
}


const a = of(1); // const a: number[]
const b = of([1]); // const b: number[]

泛型也能夠應用於重用功能,如參數 a 能夠對應類型 Type 或類型數組 Type。當 1 做爲參數傳入時,泛型Type會綁定到 number 類型 ,當傳入 [1] 時會發生一樣的狀況,Type 會被綁定到 numberget

到這裏咱們已經看到如何在函數中使用泛型,但同時,咱們也能夠將泛型與類一塊兒使用,這使得當你在 React 中編寫類組件可能會很是有趣。

class GenericClass<Type> {
  of = (a: Type | Type[]): Type[] => {
    if (Array.isArray(a)) {
      return a;
    }
    return [a];
  };
}

const genericClass = new GenericClass<number>();
const a = genericClass.of(1); // const a: number[]
const b = genericClass.of("1"); // error!
const c = genericClass.of([1]); // const c: number[]

到目前爲止咱們看到的示例應該有助於咱們理解基礎知識,咱們將基於這些知識,將泛型與React組件一塊兒使用。

React 與泛型

使用 React 時,咱們可能有一個函數組件,咱們須要推斷參數類型。這個組件須要一個 number 或者 string 類型的參數,或者一個number 或者 string 數組類型的參數。

type RowProps<Type> = {
  input: Type | Type[];
};

function Rows<Type>({input}: RowProps<Type>) {
  if (Array.isArray(input)) {
    return <div>{input.map((i, idx) => <div key={idx}>{i}</div>)}</div>
  }
  return <div>{input}</div>
}

// usage

<Rows input={[1]} />
<Rows input={1} />
<Rows input={true} /> // Also works!

這樣沒問題,可是它限制依然適用於任何值。咱們能夠傳入 true , TypeScript 不會抱怨。咱們須要嚴格限制 Type,或者讓 Type 繼承 string 或者 number 類型。

function Rows<Type extends number | string>({input}: RowProps<Type>) {
  if (Array.isArray(input)) {
    return <div>{input.map((i, idx) => <div key={idx}>{i}</div>)}</div>
  }
  return <div>{input}</div>
}

<Rows input={[1]} />
<Rows input={1} />
<Rows input="1" />
<Rows input={["1"]} />
<Rows input={true} /> //Error!

咱們能夠確保如今只能傳入預期類型的參數。值得注意的是,咱們也能夠爲 Props 定義通用類型,如上例所示:

type RowProps<Type> = {
  input: Type | Type[];
};

接下來,咱們將構建一個更高級的示例,以瞭解爲何泛型能夠幫助咱們構建可重用的React組件。咱們將構建一個指望兩個不一樣輸入的組件。基於這些輸入,咱們將計算第三個值,並根據原始輸入計算出新值,爲 render 函數提供 props

type RenderPropType<InputType, OtherInputType> = { c: number } & InputType &
  OtherInputType;

type RowComponentPropTypes<InputType, OtherInputType> = {
  input: InputType;
  otherInput: OtherInputType;
  render: (props: RenderPropType<InputType, OtherInputType>) => JSX.Element;
};

第一步是定義 RowComponentPropTypes,咱們用TypeScript推斷參數的類型,並將 RenderPropTyperender 函數的 props 進行綁定 。RenderPropType 融合了新類型 {c: number} 產生的交集,咱們將一塊兒計算,InputTypeOtherInputType。到目前爲止,咱們一直在大量使用泛型。

咱們可能不知道輸入的確切類型,所以咱們的下一步是在組件限制輸入的類型。

class RowComponent<
  InputType extends { a: number },
  OtherInputType extends { b: number }
> extends React.Component<RowComponentPropTypes<InputType, OtherInputType>> {
  // implementation...
}

經過使用 InputType extends { a: number } 咱們確保咱們的輸入具備提供number類型的a屬性。這個規則一樣適用於 OtherInputType。如今,咱們實現了 RowComponent 並確保咱們爲 render 函數提供了 abc 三個屬性。

最後,這是咱們完整的示例:

class RowComponent<
  InputType extends { a: number },
  OtherInputType extends { b: number }
> extends React.Component<RowComponentPropTypes<InputType, OtherInputType>> {
  convert = (input: InputType, output: OtherInputType) => {
    return { c: input.a + output.b, ...input, ...output };
  };
  render() {
    return this.props.render(
      this.convert(this.props.input, this.props.otherInput)
    );
  }
}

<RowComponent
  input={{ a: 1 }}
  otherInput={{ b: 2 }}
  render={({ a, b, c }) => (
    <div>
      {a} {b} {c}
    </div>
  )}
/>

如今,咱們對泛型,以及如何將 ReactTypeScript 配合使用,應該有個大體的瞭解了。

本文已經聯繫原文做者,並受權翻譯,轉載請保留原文連接
相關文章
相關標籤/搜索