React 设计模式:实用指南

devtools/2025/2/9 6:11:22/

在这里插入图片描述
React 提供了众多出色的特性以及丰富的设计模式,用于简化开发流程。开发者能够借助 React 组件设计模式,降低开发时间以及编码的工作量。此外,这些模式让 React 开发者能够构建出成果更显著、性能更优越的各类应用程序。

本文将会为您介绍五个基础的 React 组件设计模式,并提供实例来协助您优化您的 React 应用。

文章目录

    • 1. 高阶组件(HOC)模式
    • 2. Provider 模式
    • 3. 容器/表现模式
    • 4. 复合模式
    • 5. Hooks 模式

1. 高阶组件(HOC)模式

随着您的应用规模逐步扩大,可能会出现需要在多个组件之间共享相同组件逻辑的情况。而这正是 HOC 模式所能为您实现的。
HOC 是一个纯粹的 JavaScript 函数,它以一个组件作为参数,并在注入或者添加额外的数据和功能之后,返回另一个组件。从本质上讲,它充当了一个 JavaScript 装饰器函数。HOCs 的基本理念与 React 的特性相符,也就是倾向于组合而非继承。

例如,设想一个我们需要对多个应用组件进行统一风格化的场景。我们能够通过实现一个 HOC ,来避免在本地反复构建样式对象,该 HOC 会将样式应用于传入的组件。

import React from 'react';// HOC
function decoratedComponent(WrappedComponent) {return props => {const style = { padding: '5px', margin: '2px' };return <WrappedComponent style={style} {...props} />;};
}const Button = ({ style }) => <button style={{ ...style, color: 'yellow' }}>这是一个按钮。</button>;
const Text = ({ style }) => <p style={style}>这是文本。</p>;const DecoratedButton = decoratedComponent(Button);
const DecoratedText = decoratedComponent(Text);function App() {return (<><DecoratedButton /><DecoratedText /></>);
}export default App;

在上述代码示例中,我们已经对Button 和Text 组件进行了修改,分别生成了DecoratedButton 和DecoratedText。现在这两个组件都继承了由高阶组件(HOC)decoratedComponent 添加的样式。由于Button 组件已经有一个名为style 的prop,HOC 将覆盖它并附加新的prop。

优点

  • 集中维护:有助于在单一位置维护可重用功能。
  • 代码清晰:通过将所有逻辑整合到一个部分来保持代码的清晰,并实现关注点分离。
  • 减少错误:通过避免代码重复,减少整个应用中意外错误的可能性。

缺点

  • Props 名称冲突:有时会导致props 名称冲突,使得调试和扩展应用更具挑战性,特别是当组合许多共享相同prop 名称的 HOC 时。

2. Provider 模式

在复杂的 React 应用中,经常会出现如何使数据对多个组件可访问的挑战。虽然可以使用props 来传递数据,但在所有组件中访问props 值可能会变得繁琐,导致props 钻取(prop drilling)。

Provider 模式
利用 React Context API,以及在某些情况下使用 Redux,为这一挑战提供了解决方案。这种模式允许开发者将数据存储在中心区域,称为 React 上下文对象或 Redux 存储,消除了props 钻取的需要。

使用 React-Redux 实现 Provider 模式
React-Redux 在应用的顶层使用 Provider 模式,为所有组件提供对 Redux 存储的访问权限。以下代码示例展示了如何设置它。

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';const rootElement = document.getElementById('root');
ReactDOM.render(<Provider store={store}><App /></Provider>,rootElement
);

使用 React Context 的 Provider 模式
在 Redux 可能过于复杂的情况下,可以使用 React 的Context API。例如,如果一个 App 组件有一个数据集需要被深层组件树中的 List、PageHeader 和 Text 组件访问,Context API 可以绕过 props 钻取。

以下代码示例说明了如何创建和提供上下文。

import React, { createContext } from 'react';
import SideMenu from './SideMenu';
import Page from './Page';const DataContext = createContext();function App() {const data = {list: ['项目1', '项目2', '项目3'],text: '你好世界',header: 'WriterGate'}; // 在这里定义你的数据结构。return (<DataContext.Provider value={data}><SideMenu/><Page/></DataContext.Provider>);
}

使用 Context 数据
组件可以使用useContext 钩子访问数据,它允许读取和写入上下文对象中的数据。

以下代码示例供参考。

import React, { useContext } from 'react';const SideMenu = () => <List/>
const Page = () => <div><PageHeader/><Content/></div>function List() {const data = useContext(DataContext);return <span>{data.list}</span>;
}function Text() {const data = useContext(DataContext);return <h1>{data.text}</h1>;
}function PageHeader() {const data = useContext(DataContext);return <div>{data.header}</div>;
}const Content = () => {const data = useContext(DataContext);return <div><Text/></div>;
}

优点

  • 允许将数据发送到多个组件,无需通过组件层次结构。
  • 减少了重构代码时出现不可预见错误的可能性。
  • 消除了 props-drilling,这是一种反模式。
  • 简化了保持某种形式的全局状态,因为组件可以访问它。

缺点

  • 过度使用 Provider 模式可能会导致性能问题,特别是当向多个组件传递不断变化的变量时。然而,在较小的应用中,这不会是一个大问题。

3. 容器/表现模式

React 中的容器/表现模式提供了一种实现关注点分离的方法,有效地将视图与应用逻辑分开。理想情况下,我们需要通过将这个过程分成两部分来实现关注点分离。

表现组件
这些组件专注于如何向用户展示数据。它们通过props 接收数据,并负责以视觉上令人愉悦的方式呈现它,通常带有样式,而不修改数据。

考虑以下示例,它显示了从 API 获取的食物图片。为了实现这一点,我们实现了一个函数组件,该组件通过props 接收数据并相应地呈现它。

import React from "react";export default function FoodImages({ foods }) {return foods.map((food, i) => <img src={food} key={i} alt="食物" />);
}

在这个代码示例中,FoodImages 组件充当表现组件。表现组件保持无状态,除非它们需要 React 状态来渲染 UI。接收到的数据不会被修改。相反,它是从相应的容器组件中检索的。

容器组件
这些组件专注于决定向用户展示什么数据。它们的主要角色是将数据传递给表现组件。容器组件通常不渲染除了与它们的数据相关联的表现组件之外的其他组件。容器组件通常没有任何样式,因为它们的责任在于管理状态和生命周期方法,而不是渲染。

以下代码示例是一个容器组件,它从外部 API 获取图片并将它们传递给表现组件(FoodImages)。

import React from "react";
import FoodImages from "./FoodImages";export default class FoodImagesContainer extends React.Component {constructor() {super();this.state = {foods: []};}componentDidMount() {fetch("http://localhost:4200/api/food/images/random/6").then(res => res.json()).then(({ message }) => this.setState({ foods: message }));}render() {return <FoodImages foods={this.state.foods} />;}
}

优点

  • 实现了关注点分离。
  • 表现组件高度可重用。
  • 由于表现组件不改变应用逻辑,它们的外表可以在不了解源代码的情况下进行修改。
  • 测试表现组件是直接的,因为这些组件根据提供的数据进行渲染。

缺点

  • 容器/表现模式下,无状态函数组件需要被重写为类组件。

4. 复合模式

复合组件是 React 组件模式中的高级模式,它允许构建功能以协作完成任务。它允许许多相互依赖的组件共享状态和处理逻辑,同时协同工作。
这种模式为父组件与其子组件之间的通信提供了一个富有表现力和多功能的 API。此外,它允许父组件隐式地与其子组件共享状态。复合组件模式可以使用 Context API 或 React.cloneElement API 来实现。
以下代码示例展示了如何使用 Context API 实现复合组件模式。

import React, { useState, useContext } from "react";const SelectContext = React.createContext();const Select = ({ children }) => {const [activeOption, setActiveOption] = useState(null);return (<SelectContext.Provider value={{ activeOption, setActiveOption }}>{children}</SelectContext.Provider>);
};const Option = ({ value, children }) => {const context = useContext(SelectContext);if (!context) {throw new Error("Option必须在Select组件内使用。");}const { activeOption, setActiveOption } = context;return (<divstyle={activeOption === value ? { backgroundColor: "black" } : { backgroundColor: "white" }}onClick={() => setActiveOption(value)}><p>{children}</p></div>);
};// 将"Option"作为"Select"的静态属性附加。
Select.Option = Option;export default function App() {return (<Select><Select.Option value="john">John</Select.Option><Select.Option value="bella">Bella</Select.Option></Select>);
}

在上面的示例中,select 组件是一个复合组件。它由多个共享状态和行为的组件组成。我们使用「Select.Option」 =「Option」将「Option」和「Select」组件链接起来。现在,导入「Select」组件会自动包含「Option」组件。

优点

  • 复合组件维护其内部状态,这些状态在子组件之间共享。因此,在使用复合组件时无需显式管理状态。
  • 无需手动导入子组件。

缺点

  • 使用「React.Children.map」传递值时,组件堆叠受到限制。只有父组件的直接子组件才能访问 props。
  • 如果现有的 prop 与提供给「React.cloneElement」方法的 props 同名,可能会出现命名冲突。

5. Hooks 模式

React Hooks API 在 React 16.8 中引入,从根本上改变了我们处理 React 组件设计的方式。Hooks 是为了解决 React 开发者遇到的常见问题而开发的。它们通过允许函数组件访问状态、生命周期方法、上下文和 refs 等特性,彻底改变了我们编写 React 组件的方式,这些特性以前是类组件独有的。

useState
useState 钩子使得函数组件能够添加状态。它返回一个包含两个元素的数组:当前状态值和允许你更新它的函数。

import React, { useState } from "react";function ToggleButton() {const [isToggled, setIsToggled] = useState(false);const toggle = () => {setIsToggled(!isToggled);};return (<div><p>切换状态:{isToggled ? "ON" : "OFF"}</p><button onClick={toggle}>{isToggled ? "关闭" : "开启"}</button></div>);
}export default ToggleButton;

useEffect
useEffect 钩子便于在函数组件中执行副作用。它类似于类组件中的「componentDidMount」、「componentDidUpdate」和「componentWillUnmount」的组合。

import React, { useState, useEffect } from "react";function Example() {const [data, setData] = useState(null);useEffect(() => {const fetchData = async () => {try {const response = await fetch("https://jsonplaceholder.typicode.com/posts");const jsonData = await response.json();setData(jsonData);} catch (error) {console.error("获取数据错误:", error);}};fetchData();}, []);return (<div>{data ? (<div><h2>数据获取成功!</h2><ul>{data.map((item, index) => (<li key={index}>{JSON.stringify(item)}</li>))}</ul></div>) : (<p>正在加载数据...</p>)}</div>);
}export default Example;

useRef

useRef 返回一个可变的 ref 对象,其 current 属性被初始化为传递的参数。这个对象在整个组件的生命周期内持续存在。

import React, { useRef } from "react";function InputWithFocusButton() {const inputRef = useRef(null);const focusInput = () => {inputRef.current.focus();};return (<div><input ref={inputRef} type="text" /><button onClick={focusInput}>聚焦输入</button></div>);
}export default InputWithFocusButton;

优点

  • 组织代码,使其整洁清晰,不像生命周期方法那样。
  • 克服了维护挑战,利用热重载和压缩问题。
  • 允许在不编写类的情况下利用状态和其他 React 功能。
  • 促进了跨组件重用有状态逻辑,减少代码重复。
  • 减少了错误的可能性,并使用简单函数实现组合。

缺点

  • 需要遵守特定规则,尽管没有 linter 插件很难识别规则违规。
  • 需要实践才能有效使用某些钩子(例如,useEffect)。
  • 需要小心避免不当使用(例如,useCallback,useMemo)。

http://www.ppmy.cn/devtools/157265.html

相关文章

如何在WPS和Word/Excel中直接使用DeepSeek功能

以下是将DeepSeek功能集成到WPS中的详细步骤&#xff0c;无需本地部署模型&#xff0c;直接通过官网连接使用&#xff1a;1. 下载并安装OfficeAI插件 &#xff08;1&#xff09;访问OfficeAI插件下载地址&#xff1a;OfficeAI助手 - 免费办公智能AI助手, AI写作&#xff0c;下载…

Spring Boot统一异常拦截实践指南

Spring Boot统一异常拦截实践指南 一、为什么需要统一异常处理 在Web应用开发中&#xff0c;异常处理是保证系统健壮性和用户体验的重要环节。传统开发模式中常见的痛点包括&#xff1a; 异常处理逻辑分散在各个Controller中错误响应格式不统一敏感异常信息直接暴露给客户端…

tcpdump能否抓到被iptable封禁的包

tcpdump 能否抓到被 iptable 封禁的包? tcpdump工作在设备层&#xff0c;将包送到IP层以前就能处理。而netfiter工作在IP、ARP等层。从图2.13收包流程处理顺序上来看&#xff0c;netfiter是在tcpdump后面工作的&#xff0c;所以iptable封禁规则影响不到tcpdump的抓包。 不过发…

Effective Objective-C 2.0 读书笔记—— 接口与API设计

Effective Objective-C 2.0 读书笔记—— 接口与API设计 文章目录 Effective Objective-C 2.0 读书笔记—— 接口与API设计1. 用前缀避免命名空间冲突2.提供"全能初始化方法"3.实现description方法4.尽量使用不可变对象5.理解Objective -C错误模型 1. 用前缀避免命名…

【通俗易懂说模型】线性回归(附深度学习、机器学习发展史)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;深度学习_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. …

CVPR | CNN融合注意力机制,芜湖起飞!

**标题&#xff1a;**On the Integration of Self-Attention and Convolution **论文链接&#xff1a;**https://arxiv.org/pdf/2111.14556 **代码链接&#xff1a;**https://github.com/LeapLabTHU/ACmix 创新点 1. 揭示卷积和自注意力的内在联系 文章通过重新分解卷积和自…

SpringBoot3与MyBatis-Plus

4.1 介绍 MyBatis-Plus&#xff08;简称 MP&#xff09;是一个基于 MyBatis 的增强工具&#xff0c;提供通用 CRUD 操作、代码生成器、条件构造器、分页插件等功能&#xff0c;简化开发流程&#xff0c;提升效率。 4.2 特点 无侵入&#xff1a;只做增强不做修改&#xff0c;与…

树和二叉树_6

树和二叉树_6 一、leetcode-105二、题解1.引库2.代码 一、leetcode-105 从前序与中序遍历序列构造二叉树 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节…