React 设计模式:实用指南

在这里插入图片描述
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.mzph.cn/pingmian/69369.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoo项目标准测试样例

文章目录 概要Controller Api 测试源码单元测试集成测试 概要 Spring Boot项目测试用例 测试方式是否调用数据库使用的注解特点单元测试&#xff08;Mock Service&#xff09;❌ 不调用数据库WebMvcTest MockBean只测试 Controller 逻辑&#xff0c;速度快集成测试&#xff0…

Unity扩展编辑器使用整理(一)

准备工作 在Unity工程中新建Editor文件夹存放编辑器脚本&#xff0c; Unity中其他的特殊文件夹可以参考官方文档链接&#xff0c;如下&#xff1a; Unity - 手册&#xff1a;保留文件夹名称参考 (unity3d.com) 一、菜单栏扩展 1.增加顶部菜单栏选项 使用MenuItem&#xff…

Vue3+codemirror6实现公式(规则)编辑器

实现截图 实现/带实现功能 插入标签 插入公式 提示补全 公式验证 公式计算 需要的依赖 "codemirror/autocomplete": "^6.18.4","codemirror/lang-javascript": "^6.2.2","codemirror/state": "^6.5.2","cod…

K8S QoS等级

在 Kubernetes (K8S) 中&#xff0c;QoS&#xff08;Quality of Service&#xff0c;服务质量&#xff09;等级用于定义 Pod 在资源调度和管理过程中的优先级&#xff0c;确保在资源紧张时能够更好地管理和分配资源。Kubernetes 根据 Pod 的资源请求和限制将 Pod 分为三种 QoS …

4.PPT:日月潭景点介绍【18】

目录 NO1、2、3、4​ NO5、6、7、8 ​ ​NO9、10、11、12 ​ 表居中或者水平/垂直居中单元格内容居中或者水平/垂直居中 NO1、2、3、4 新建一个空白演示文稿&#xff0c;命名为“PPT.pptx”&#xff08;“.pptx”为扩展名&#xff09;新建幻灯片 开始→版式“PPT_素材.doc…

如何在macOS上安装Ollama

安装Ollama 安装Ollama的步骤相对简单&#xff0c;以下是基本的安装指南&#xff1a; 访问官方网站&#xff1a;打开浏览器&#xff0c;访问Ollama的官方网站。 下载安装包&#xff1a;根据你的操作系统&#xff0c;选择相应的安装包进行下载。 运行安装程序&#xff1a;下载完…

开源项目介绍-词云生成

开源词云项目是一个利用开源技术生成和展示词云的工具或框架&#xff0c;广泛应用于文本分析、数据可视化等领域。以下是几个与开源词云相关的项目及其特点&#xff1a; Stylecloud Stylecloud 是一个由 Maximilianinir 创建和维护的开源项目&#xff0c;旨在通过扩展 wordclou…

Redis双写一致性(数据库与redis数据一致性)

一 什么是双写一致性&#xff1f; 当修改了数据库&#xff08;MySQL&#xff09;中的数据&#xff0c;也要同时更新缓存&#xff08;redis&#xff09;中的数据&#xff0c;缓存中的数据要和数据库中的数据保持一致 双写一致性&#xff0c;根据业务对时间上的要求&#xff0c;…

笔记:新能源汽车零部件功率级测试怎么进行?

摘要:本文旨在梳理主机厂对新能源汽车核心零部件功率级测试需求,通过试验室的主流设备仪器集成,快速实现试验方案搭建,并体现测试测量方案的时效性、便捷性优势。目标是通过提升实现设备的有效集成能力、实现多设备测试过程的有效协同、流程化测试,可快速采集、分析当前数…

C32.【C++ Cont】静态实现双向链表及STL库的list

目录 1.知识回顾 2.静态实现演示图 3.静态实现代码 1.初始双向链表 2.头插 3.遍历链表 4.查找某个值 4.任意位置之后插入元素 5.任意位置之前插入元素 6.删除任意位置的元素 4.STL库的list 1.知识回顾 96.【C语言】数据结构之双向链表的初始化,尾插,打印和尾删 97.【C…

二级C语言题解:矩阵主、反对角线元素之和,二分法求方程根,处理字符串中 * 号

目录 一、程序填空&#x1f4dd; --- 矩阵主、反对角线元素之和 题目&#x1f4c3; 分析&#x1f9d0; 二、程序修改&#x1f6e0;️ --- 二分法求方程根 题目&#x1f4c3; 分析&#x1f9d0; 三、程序设计&#x1f4bb; --- 处理字符串中 * 号 题目&#x1f…

采用idea中的HTTP Client插件测试

1.安装插件 采用idea中的HTTP Client插件进行接口测试,好处是不用打开post/swagger等多个软件,并且可以保存测试时的参数,方便后续继续使用. 高版本(2020版本以上)的idea一般都自带这个插件,如果没有也可以单独安装. 2.使用 插件安装完成(或者如果idea自带插件),会在每个Con…

探讨如何在AS上构建webrtc(2)从sdk/android/Build.gn开始

全文七千多字&#xff0c;示例代码居多别担心&#xff0c;没有废话&#xff0c;不建议跳读。 零、梦开始的地方 要发美梦得先入睡&#xff0c;要入睡得找能躺平的地方。那么能躺平编译webrtc-android的地方在哪&#xff1f;在./src/sdk/android/Build.gn。Build.gn是Build.nin…

Linux firewalld开启日志审计功能(2)

在Firewalld防火墙中启用和配置logdenied选项&#xff0c;记录被拒绝的数据包&#xff08;等同于开启日志功能&#xff09; 效果展示&#xff1a; 1.开启日志记录功能 firewall-cmd --set-log-deniedunicast #重新加载生效配置 firewall-cmd --reload 2.配置rsyslog捕获日志…

Spring Web MVC项目的创建及使用

一、什么是Spring Web MVC&#xff1f; Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架&#xff0c;从⼀开始就包含在 Spring 框架中&#xff0c;通常被称为Spring MVC。 1.1 MVC的定义 MVC 是 Model View Controller 的缩写&#xff0c;它是软件工程中的一种软件架构…

oracle:索引(B树索引,位图索引,分区索引,主键索引,唯一索引,联合索引/组合索引,函数索引)

索引通过存储列的排序值来加快对表中数据的访问速度&#xff0c;帮助数据库系统快速定位到所需数据&#xff0c;避免全表扫描 B树索引(B-Tree Index) B树索引是一种平衡树结构&#xff0c;适合处理范围查询和精确查找。它的设计目标是保持数据有序&#xff0c;并支持高效的插入…

Java基础学习笔记-static关键字

>一个类可以创建n个对象。如果n个对象中的某些数据需要共用&#xff0c;就需要使用static关键字修 饰这些数据。 >Java中&#xff0c;一般情况调用类的成员都需要先创建类的对象&#xff0c;然后通过对象进行调用。使用static关键字可以实现通过类名加“.”直接调用类的…

LeetCode Java面试刷题笔记汇总

LeetCode Java刷题笔记汇总&#xff0c;按照类型刷题效率更高。刷题前需要先学习数据结构与算法的基础知识&#xff1a;Java 数据结构与算法。 大厂面试算法题有一定的运气成分&#xff0c;有可能你刷的比较少&#xff0c;但是遇到会的题就进去了&#xff0c;也有可能你刷的比…

android 适配 api 35(android 15) 遇到的问题

首先升级 targetSdkVersion 和 compileSdkVersion 到 35&#xff0c;升级后发生的报错 一、 解决方案: 升级 gradle 和 gradle 插件版本 com.android.tools.build:gradle -> 8.3.0-alpha02 gradle-wrapper.properties : distributionUrl -> gradle-8.6-bin.zip htt…

@Value属性读取系统变量错误

Value属性读取配置属性错误 场景 在测试Value读取yml配置文件属性时&#xff0c;发现系统配置属性优先级高于配置文件&#xff0c;导致注入异常值&#xff1a; 配置文件: user:name: yanxin测试类: RestController RequestMapping("/books") public class BookC…