【譯】TypeScript筆記:React和泛型

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

介紹

這些筆記應該有助於更好的理解 TypeScript,並且在查找如何在特定情況下使用 TypeScript 會很有幫助。所有示例都是基於 TypeScript 3.2 完成。

泛型

如果您一直在閱讀“TypeScript 筆記”系列,那麼到目前爲止您將看到泛型的廣泛使用。雖然我們一直在使用泛型,但是我們並沒有真正的介紹泛型,以及它有什麼用。在本系列的這一部分中,我們將首先嚐試更好的理解泛型的概念,然後瞭解如何將泛型和 React 和 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 將嘗試推斷參數,並指定一個正確的類型。讓我們接着看看下一個例子,推斷一下返回的類型:

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[]

從技術上講,我們可以使用 any

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];
}

在處理泛型時我們必須更加明確,可以將示例重寫爲以下內容:

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 會被綁定到 number

到這裏我們已經看到如何在函數中使用泛型,但同時,我們也可以將泛型與類一起使用,這使得當你在 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 配合使用,應該有個大致的瞭解了。

本文已經聯繫原文作者,並授權翻譯,轉載請保留原文鏈接
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章