React + TypeScript 實現泛型組件



泛型類型

TypeScript 中,類型(interface, type)是可以聲明成泛型的,這很常見。

interface Props<T> {
  content: T;
}

這表明 Props 接口定義了這么一種類型:

  • 它是包含一個 content 字段的對象
  • content 字段的類型由使用時的泛型 T 決定
type StringProps = Props<string>;

let props: StringProps;

props = {
// 🚨 Type 'number' is not assignable to type 'string'.ts(2322)
content: 42
};

props = {
//
content: "hello"
};

或者,TypeScript 能夠跟使用時候提供的值自動推斷出類型 T,無需顯式指定:

interface Props<T> {
  content: T;
}

function Foo<T>(props: Props<T>) {
console.log(props);
}

/** 此時 Foo 的完整簽名為: function Foo<number>(props: Props<number>): void */
Foo({ content: 42 });

/** 此時 Foo 的完整簽名為: function Foo<string>(props: Props<string>): void */
Foo({ content: "hello" });

上面因為 Foo 函數接收 Props<T> 作為入參,意味着我們在調用 Foo 的時候需要傳遞類型 T 以確定 Props<T>,所以 Foo 函數也變成了泛型。

當調用 Foo({ content: 42 }) 的時候,TypeScript 自動解析出 Tnumber,此時對應的函數簽名為:

function Foo<number>(props: Props<number>): void;

而我們並沒有顯式地指定其中的類型 T,像這樣 Foo<number>({ content: 42 });

泛型組件

將上面的 Foo 函數返回 JSX 元素,就成了一個 React 組件。因為它是泛型函數,它所形成的組件也就成了 泛型組件/Generic Components

function Foo<T>(props: Props<T>) {
  return <div> {props.content}</div>;
}

const App = () => {
return (
<div className="App">
<Foo content={42}></Foo>
<Foo<string> content={"hello"}></Foo>
</div>
);
};

一如上面的討論,因為 TypeScript 可根據傳入的實際值解析泛型類型,所以 <Foo<string> content={"hello"}></Foo>string 是可選的,這里只為展示,讓你看到其實 React 組件還可以這么玩。

為了進一步理解泛型組件,再看下非泛型情況下上面的組件是長怎樣的。

interface Props {
  content: string;
}

function Foo(props: Props) {
return <div>{props.content}</div>;
}

const App = () => {
return (
<div className="App">
{/ 🚨 Type 'number' is not assignable to type 'string'.ts(2322) /}
<Foo content={42}></Foo>
<Foo content={"hello"}></Foo>
</div>
);
};

以上,便是一個 React 組件常規的寫法。它定義的入參 Props 只接收 string 類型。由此也看出泛型的優勢,即大部分代碼可復用的情況下,將參數變成泛型后,不同類型的入參可復用同一組件,不用為新類型新寫一個組件。

除了函數組件,對於類類型的組件來說,也是一樣可泛型化的。

interface Props<T> {
  content: T;
}

class Bar<T> extends React.Component<Props<T>> {
render() {
return <div>{this.props.content}</div>;
}
}

const App = () => {
return (
<div className="App">
<Bar content={42}></Bar>
<Bar<string> content={"hello"}></Bar>
</div>
);
};

一個更加真實的示例

一個更加實用的示例是列表組件。列表中的分頁加載,滾動刷新邏輯等,對於所有列表數據都是通用的,將這個列表組件書寫成泛型便可和任意類型列表數據結合,而無須通過其他方式來達到復用的目的,將列表元素聲明成 anyRecord<string,any> 等類型。

先看不使用泛型情況下,如何實現這么一個列表組件。此處只看列表元素的展示以闡述泛型的作用,其他邏輯比如數據加載等先忽略。

列表組件 List.tsx

interface Item {
  [prop: string]: any;
}

interface Props {
list: Item[];
children: (item: Item, index: number) => React.ReactNode;
}

function List({ list, children }: Props) {
// 列表中其他邏輯...
return <div>{list.map(children)}</div>;
}

上面,為了盡可能滿足大部分數據類型,將列表的元素類型定義成了 [prop: string]: any; 的形式,其實和 Record<string,any> 沒差。在這里已經可以看到類型的丟失了,因為出現了 any,而我們使用 TypeScript 的首要准則是盡量避免 any

然后是使用上面所定義的列表組件:

interface User {
  id: number;
  name: string;
}
const data: User[] = [
  {
    id: 1,
    name: "wayou"
  },
  {
    id: 1,
    name: "niuwayong"
  }
];

const App = () => {
return (
<div className="App">
<List list={data}>
{item => {
// 😭 此處 item.name 類型為 any
return <div key={item.name}>{item.name}</div>;
}}
</List>
</div>
);
};

這里使用時,item.name 的類型已經成了 any。對於簡單數據來說,還可以接收這樣類型的丟失,但對於復雜類型,類型的丟失就完全享受不到 TypeScript 所帶來的類型便利了。

上面的實現還有個問題是它規定了列表元素必需是對象,理所應當地就不能處理元始類型數組了,比如無法渲染 ['wayou','niuwayong'] 這樣的輸入。

下面使用泛型改造上面的列表組件,讓它支持外部傳入類型。

interface Props<T> {
  list: T[];
  children: (item: T, index: number) => React.ReactNode;
}

function List<T>({ list, children }: Props<T>) {
// 列表中其他邏輯...
return <div>{list.map(children)}</div>;
}

改造后,列表元素的類型完全由使用的地方決定,作為列表組件,內部它無須關心,同時對於外部傳遞的 children 回調中 item 入參,類型也沒有丟失。

使用改造后的泛型列表:

interface User {
  id: number;
  name: string;
}
const data: User[] = [
  {
    id: 1,
    name: "wayou"
  },
  {
    id: 1,
    name: "niuwayong"
  }
];

const App = () => {
return (
<div className="App">
<List list={data}>
{item => {
// 😁 此處 item 類型為 User
return <div key={item.name}>{item.name}</div>;
}}
</List>
<List list={["wayou", "niuwayong"]}>
{item => {
// 😁 此處 item 類型為 string
return <div key={item}>{item}</div>;
}}
</List>
</div>
);
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM