通过三个实例掌握如何使用TypeScript泛型创建可重用的Re...

程序员咋不秃头 2024-06-22 11:23:03

在前端开发中,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 函数中预期的类型匹配。

使用泛型组件渲染任务列表

最后,我们来看看如何用泛型组件渲染一个任务列表。

import { useState } from 'react'import GenericComponent from './GenericComponent'interface TodoItem { id: number title: string completed: boolean}export function TodoList() { // 初始任务列表 const initialTodos: TodoItem[] = [ { id: 1, title: 'Learn React', completed: false }, { id: 2, title: 'Write blog post', completed: true }, { id: 3, title: 'Study TypeScript', completed: false }, ] // 用于追踪任务列表的状态 const [todos, setTodos] = useState<TodoItem[]>(initialTodos) // 切换任务完成状态的函数 function toggleTodoCompletion(id: number) { const updatedTodos = todos.map((todo) => (todo.id === id ? { ...todo, completed: !todo.completed } : todo)) setTodos(updatedTodos) } // 任务列表的复杂渲染函数 function renderTodos(todos: TodoItem[]) { return ( <ul> {todos.map((todo) => ( <li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none', cursor: 'pointer' }} onClick={() => toggleTodoCompletion(todo.id)} > {todo.title} </li> ))} </ul> ) } return <GenericComponent<TodoItem[]> data={todos} render={renderTodos} />}

在这个例子中,我们创建了一个 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 这样的库来处理表单,但为了演示泛型的强大之处,我们将从头开始创建一个简单的表单组件。

定义表单字段和组件的类型

首先,我们定义一些 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 组件更加灵活和可重用。通过使用泛型,你可以创建适用于任何数据类型的组件,这在处理各种数据类型的实际应用中尤为有用。

0 阅读:1

程序员咋不秃头

简介:感谢大家的关注