![](http://image.uc.cn/s/wemedia/s/upload/2024/a57c314f7e5e02243992c1ad6c1f58f3.jpg)
在前端开发中,React 组件是我们日常开发的基础,而使用 TypeScript 泛型能让这些组件更加灵活和可重用。今天我们就来聊聊如何利用 TypeScript 泛型创建可重用的 React 组件。
在深入具体操作之前,先简单介绍一下泛型的概念。泛型允许你在定义组件时不指定具体的数据类型,而是在使用组件时再指定具体的类型。这样一来,我们的组件就能够适应多种数据类型,不必为每种数据类型分别创建不同的组件。
市面上已经有很多关于 TypeScript 泛型的文章和教程,所以本文将聚焦于如何在 React 组件中使用泛型,让你的组件变得更加灵活和可重用。
接下来,我们将通过实例代码一步步展示如何实现这一目标,让你能够轻松掌握这项技能,并应用到实际项目中去。无论你是刚入门的新手,还是有一定经验的开发者,相信都能从中受益。准备好了吗?让我们开始吧!
一、利用 TypeScript 泛型创建简单的可重用 React 组件创建一个简单的泛型 React 组件
首先,我们来创建一个泛型 React 组件,它可以接受任何类型的数据并通过一个渲染函数将数据展示出来。
// 定义一个泛型类型的 propstype Props<T> = { data: T render: (data: T) => React.ReactNode}// 创建一个泛型 React 组件export function GenericComponent<T>({ data, render }: Props<T>) { return <div>{render(data)}</div>}在这个例子中,GenericComponent 接受一个 render 属性,这个属性是一个函数,它接收类型为 T 的数据并返回一个 React.ReactNode。这种模式通常被称为“render props”,它可以让你更灵活地控制数据的渲染方式。
使用泛型组件渲染字符串
接下来,我们用一个字符串类型的数据来使用这个泛型组件。
import GenericComponent from './GenericComponent'function RenderString() { return ( <GenericComponent<string> data="Hello, world!" render={(data) => <span>{data.toUpperCase()}</span>} /> )}在这个例子中,我们明确地告诉 GenericComponent 预期接收一个字符串类型的数据。渲染函数将字符串转换为大写,并且 TypeScript 确保了在 render 属性中进行的操作是对字符串类型数据有效的。
使用自定义类型的数据
现在我们用一个自定义类型的数据来使用泛型组件。
type Person = { name: string age: number}import GenericComponent from './GenericComponent'function RenderPerson() { return ( <GenericComponent<Person> data={{ name: 'Alice', age: 30 }} render={(data) => ( <span> {data.name} is {data.age} years old </span> )} /> )}在这个例子中,TypeScript 确保 data 属性的数据类型与 render 函数中预期的类型匹配。
使用泛型组件渲染任务列表
最后,我们来看看如何用泛型组件渲染一个任务列表。
![](http://image.uc.cn/s/wemedia/s/upload/2024/fce8067260b02e28bae40b4826ce732b.jpg)
在这个例子中,我们创建了一个 TodoList 组件,它使用 GenericComponent 渲染一个任务列表。渲染函数更加复杂,因为它需要处理一个项目列表。TypeScript 确保 data 属性的数据类型与 render 函数中预期的类型匹配。
二、使用泛型在 React 组件中展示数据在实际开发中,很多时候我们需要从 API 获取数据并展示在页面上。利用 TypeScript 泛型,我们可以创建一个通用的 React 组件来处理这种情况。这样不仅能提高代码的可重用性,还能使组件更加灵活。今天我们就通过一个例子来展示如何实现这一目标。
创建一个用于获取数据的泛型 React 组件
首先,我们创建一个泛型组件 FetchAndDisplay,它可以从指定的 URL 获取数据,并通过一个渲染函数将数据展示出来。
import { useEffect, useState } from 'react'// 定义一个泛型类型的 propstype Props<T> = { url: string render: (data: T) => React.ReactNode}// 创建一个泛型 React 组件export function FetchAndDisplay<T>({ url, render }: Props<T>) { const [data, setData] = useState<T | null>(null) const [loading, setLoading] = useState(true) const [error, setError] = useState<Error | null>(null) useEffect(() => { const fetchData = async () => { try { const response = await fetch(url) if (!response.ok) { throw new Error('Failed to fetch data') } const json = await response.json() setData(json) } catch (error) { setError(error) } finally { setLoading(false) } } fetchData() }, [url]) if (loading) { return <div>Loading...</div> } if (error) { return <div>Error: {error.message}</div> } if (data) { return <div>{render(data)}</div> } return null}在这个例子中,FetchAndDisplay 是一个泛型组件,它接受一个 URL 和一个渲染函数。组件使用 fetch 方法从指定的 URL 抓取数据,并在抓取成功后调用渲染函数来展示数据。同时,组件还处理了加载和错误状态。
使用 FetchAndDisplay 组件获取和展示帖子
接下来,我们使用 FetchAndDisplay 组件来取取并展示一组帖子数据。
import { FetchAndDisplay } from './FetchAndDisplay'type Post = { userId: number id: number title: string body: string}function RenderPosts(posts: Post[]) { return ( <ul> {posts.map((post) => ( <li key={post.id}> <h3>{post.title}</h3> <p>{post.body}</p> </li> ))} </ul> )}export function PostList() { return <FetchAndDisplay<Post[]> url='https://jsonplaceholder.typicode.com/posts' render={RenderPosts} />}在这个例子中,我们使用 FetchAndDisplay 组件从 JSONPlaceholder API 获取一组帖子数据,并通过 RenderPosts 函数将其展示出来。
使用 FetchAndDisplay 组件获取和展示用户
同样地,我们可以使用同一个 FetchAndDisplay 组件来抓取和展示用户数据。
import { FetchAndDisplay } from './FetchAndDisplay'type User = { id: number name: string email: string}function RenderUsers(users: User[]) { return ( <ul> {users.map((user) => ( <li key={user.id}> <h3>{user.name}</h3> <p>{user.email}</p> </li> ))} </ul> )}export function UserList() { return <FetchAndDisplay<User[]> url='https://jsonplaceholder.typicode.com/users' render={RenderUsers} />}在这个例子中,我们使用 FetchAndDisplay 组件从 JSONPlaceholder API 获取一组用户数据,并通过 RenderUsers 函数将其展示出来。这展示了泛型在 React 组件中的强大作用,我们可以用同一个组件处理不同类型的数据获取和展示。
三、使用泛型创建通用的 React 表单组件在实际开发中,表单是我们常用的组件之一。为了提升代码的复用性和灵活性,我们可以使用 TypeScript 泛型创建一个通用的表单组件。尽管在实际项目中我们通常会使用像 Formik 或 react-hook-form 这样的库来处理表单,但为了演示泛型的强大之处,我们将从头开始创建一个简单的表单组件。
![](http://image.uc.cn/s/wemedia/s/upload/2024/8dee7334b670e1141d88fe138f5275d9.jpg)
定义表单字段和组件的类型
首先,我们定义一些 TypeScript 类型,用来指定表单字段的结构以及我们的通用表单组件将接受的 props。这些类型确保了类型安全,并帮助我们管理表单的状态和行为。
type FormField = { name: string label: string type: 'text' | 'email' | 'password' // 可以根据需要扩展}type GenericFormProps<T> = { fields: FormField[] initialValues: T onSubmit: (values: T) => void}创建通用表单组件
接下来,我们创建一个函数组件,它接受字段、初始值和一个提交处理函数作为参数。
import { useState } from 'react'export function GenericForm<T>({ fields, initialValues, onSubmit }: GenericFormProps<T>) { const [values, setValues] = useState<T>(initialValues) function handleChange(e: React.ChangeEvent<HTMLInputElement>) { const { name, value } = e.target setValues((prevValues) => ({ ...prevValues, [name]: value })) } function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault() onSubmit(values) } return ( <form onSubmit={handleSubmit}> {fields.map((field) => ( <div key={field.name}> <label htmlFor={field.name}>{field.label}</label> <input type={field.type} name={field.name} value={(values as any)[field.name]} onChange={handleChange} /> </div> ))} <button type='submit'>Submit</button> </form> )}使用通用表单组件
最后,我们使用通用表单组件,并指定具体的表单字段和初始值。
type UserFormValues = { name: string email: string password: string}const userFormFields: FormField[] = [ { name: 'name', label: 'Name', type: 'text' }, { name: 'email', label: 'Email', type: 'email' }, { name: 'password', label: 'Password', type: 'password' },]const initialValues: UserFormValues = { name: '', email: '', password: '',}function App() { const handleSubmit = (values: UserFormValues) => { console.log('Form Submitted', values) } return ( <div> <h1>User Registration</h1> <GenericForm fields={userFormFields} initialValues={initialValues} onSubmit={handleSubmit} /> </div> )}在这个例子中,如果不使用泛型,你需要为每种类型的表单分别创建一个表单组件。使用泛型后,你可以创建一个通用的表单组件,可以用于任何类型的表单字段。这展示了泛型在 React 组件中的强大作用,使得我们的组件更加灵活和可复用。
附加示例:使用泛型创建通用的表格组件
在开发中,表格组件是一个常见的需求。为了使表格组件更加灵活和可重用,我们可以使用 TypeScript 泛型来创建一个通用的表格组件。通过这种方式,我们可以确保数据类型的一致性,并能够轻松地渲染不同类型的数据。
创建通用表格组件
首先,我们定义一个通用表格组件 Table,它接受一组行数据和一个用于渲染行的函数。
import React from 'react'const Table = <TRow extends Record<string, any>>(props: { rows: TRow[]; renderRow: React.FC<TRow>;}) => { return ( <table> <tbody> {props.rows.map((row, index) => ( <props.renderRow key={index} {...row} /> ))} </tbody> </table> )}在这个例子中,Table 组件接受一个泛型参数 TRow,它表示表格中每一行的数据类型。组件接收一个 rows 数组和一个 renderRow 函数。renderRow 函数负责渲染每一行的数据。
使用通用表格组件
接下来,我们使用 Table 组件来渲染一个包含姓名和年龄的表格。
import React from 'react'import { Table } from './Table'type Person = { name: string age: number}const people: Person[] = [ { name: 'Alice', age: 30 }, { name: 'Bob', age: 40 },]const RenderPersonRow: React.FC<Person> = ({ name, age }) => ( <tr> <td>{name}</td> <td>{age}</td> </tr>)const App = () => { return ( <div> <h1>People Table</h1> <Table rows={people} renderRow={RenderPersonRow} /> </div> )}export default App在这个例子中,我们定义了一个 Person 类型,它包含 name 和 age 两个属性。然后我们创建了一个 people 数组,包含两个人的姓名和年龄。RenderPersonRow 是一个用于渲染每行数据的组件,它接受 Person 类型的属性并返回一个表格行。
我们在 App 组件中使用 Table 组件,将 people 数组作为 rows 传递,并将 RenderPersonRow 作为 renderRow 函数传递给 Table 组件。这样,表格组件就会渲染包含两行数据的表格,每行数据对应一个人的姓名和年龄。
结束TypeScript 的泛型是一项强大的功能,能够使你的 React 组件更加灵活和可重用。通过使用泛型,你可以创建适用于任何数据类型的组件,这在处理各种数据类型的实际应用中尤为有用。