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 (
        <div
            style={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)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/965629.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++11详解(三) -- 可变参数模版和lambda

文章目录 1.可变模版参数1.1 基本语法及其原理1.2 包扩展1.3 empalce系列接口1.3.1 push_back和emplace_back1.3.2 emplace_back在list中的使用&#xff08;模拟实现&#xff09; 2. lambda2.1 lambda表达式语法2.2 lambda的捕捉列表2.3 lambda的原理 1.可变模版参数 1.1 基本…

【数据结构】_时间复杂度相关OJ(力扣版)

目录 1. 示例1&#xff1a;消失的数字 思路1&#xff1a;等差求和 思路2&#xff1a;异或运算 思路3&#xff1a;排序&#xff0b;二分查找 2. 示例2&#xff1a;轮转数组 思路1&#xff1a;逐次轮转 思路2&#xff1a;三段逆置&#xff08;经典解法&#xff09; 思路3…

OSPF基础(2):数据包详解

OSPF数据包(可抓包) OSPF报文直接封装在IP报文中&#xff0c;协议号89 头部数据包内容&#xff1a; 版本(Version):对于OSPFv2&#xff0c;该字段值恒为2(使用在IPV4中)&#xff1b;对于OSPFv3&#xff0c;该字段值恒为3(使用在IPV6中)。类型(Message Type):该OSPF报文的类型。…

第二篇:前端VSCode常用快捷键-以及常用技巧

继续书接上一回&#xff0c; 我们讲解了常用的vscode 插件。 vscode 常用的插件地址&#xff1a; 前端VSCode常用插件-CSDN博客 本篇文章&#xff0c;主要介绍vscode常用的快捷键&#xff0c;可以提高我们的开发效率。 一、VSCode常用的快捷键 注意&#xff0c;其实这个快捷…

【LeetCode】152、乘积最大子数组

【LeetCode】152、乘积最大子数组 文章目录 一、dp1.1 dp1.2 简化代码 二、多语言解法 一、dp 1.1 dp 从前向后遍历, 当遍历到 nums[i] 时, 有如下三种情况 能得到最大值: 只使用 nums[i], 例如 [0.1, 0.3, 0.2, 100] 则 [100] 是最大值使用 max(nums[0…i-1]) * nums[i], 例…

vue生命周期及其作用

vue生命周期及其作用 1. 生命周期总览 2. beforeCreate 我们在new Vue()时&#xff0c;初始化一个Vue空的实例对象&#xff0c;此时对象身上只有默认的声明周期函数和事件&#xff0c;此时data,methods都未被初始化 3. created 此时&#xff0c;已经完成数据观测&#xff0…

什么是三层交换技术?与二层有什么区别?

什么是三层交换技术&#xff1f;让你的网络飞起来&#xff01; 一. 什么是三层交换技术&#xff1f;二. 工作原理三. 优点四. 应用场景五. 总结 前言 点个免费的赞和关注&#xff0c;有错误的地方请指出&#xff0c;看个人主页有惊喜。 作者&#xff1a;神的孩子都在歌唱 大家好…

e2studio开发RA2E1(5)----GPIO输入检测

e2studio开发RA2E1.5--GPIO输入检测 概述视频教学样品申请硬件准备参考程序源码下载新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置GPIO口配置按键口配置按键口&Led配置R_IOPORT_PortRead()函数原型R_IOPORT_PinRead()函数原型代码 概述 本篇文章主要介绍如何…

【LLM】为何DeepSeek 弃用MST却采用Rejection采样

文章目录 拒绝采样 Rejection sampling&#x1f3af;马尔可夫搜索树 &#x1f333;RFT和SFT1. RFT和SFT的区别2. 如何将RFT用于数学推理任务&#xff1f; Reference 在提升大语言模型&#xff08;LLM&#xff09;推理能力时&#xff0c;拒绝采样&#xff08;Rejection Sampling…

股指入门:股指期货是什么意思?在哪里可以做股指期货交易?

股指期货是一种以股票指数为标的物的期货合约&#xff0c;也可以称为股票指数期货或期指。 股指期货是什么意思&#xff1f; 股指期货是一种金融衍生品&#xff0c;其标的资产是股票市场上的股指&#xff0c;例如标普500指数、道琼斯工业平均指数、上证50指数等。 股指期货允…

前端构建工具大比拼:Vite、Webpack、Parcel、esbuild 等热门工具使用分析

前端构建工具大比拼&#xff1a;Vite、Webpack、Parcel、esbuild 等热门工具使用分析 随着前端技术的不断发展&#xff0c;构建工具成为了每个前端项目的核心部分。通过合适的构建工具&#xff0c;我们能够优化开发效率、提升构建速度&#xff0c;并最终实现更加高效和灵活的开…

安装和使用 Ollama(实验环境windows)

下载安装 下载 https://ollama.com/download/windows 安装 Windows 安装 如果直接双击 OllamaSetup.exe 安装&#xff0c;默认会安装到 C 盘&#xff0c;如果需要指定安装目录&#xff0c;需要通过命令行指定安装地址&#xff0c;如下&#xff1a; # 切换到安装目录 C:\Use…

node.js使用mysql2对接数据库

一、引言 在现代Web开发中&#xff0c;Node.js作为一种高效、轻量级的JavaScript运行时环境&#xff0c;已经广泛应用于后端服务的开发中。而MySQL&#xff0c;作为一个广泛使用的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;提供了强大的数据存储和查询功能…

Unity 快速入门 1 - 界面操作

本项目将快速介绍 Unity 6的基本操作和功能&#xff0c;下载附件的项目&#xff0c;解压到硬盘&#xff0c;例如 D:\Unity Projects\&#xff0c; 注意整个文件路径中只有英文、空格或数字&#xff0c;不要有中文或其他特殊符合。 1. 打开Unity Hub&#xff0c;点击右上角的 O…

携程Java开发面试题及参考答案 (200道-上)

说说四层模型、七层模型。 七层模型(OSI 参考模型) 七层模型,即 OSI(Open System Interconnection)参考模型,是一种概念模型,用于描述网络通信的架构。它将计算机网络从下到上分为七层,各层的功能和作用如下: 物理层:物理层是计算机网络的最底层,主要负责传输比特流…

云轴科技ZStack+海光DCU:率先推出DeepSeek私有化部署方案

针对日益强劲的AI推理需求和企业级AI应用私有化部署场景&#xff08;Private AI&#xff09;&#xff0c;云轴科技ZStack联合海光信息&#xff0c;共同推动ZStack智塔全面支持DeepSeek V3/R1/Janus Pro系列模型&#xff0c;基于海光DCU实现高性能适配&#xff0c;为企业提供安全…

通信易懂唠唠SOME/IP——SOME/IP协议简介

一 简介 1.1 面向服务的中间件 SOME/IP是Scalable service-Oriented MiddlewarE over IP (SOME/IP)的缩写&#xff0c;基于IP的可扩展面向服务的中间件。 1.2 广泛应用于汽车嵌入式通信 SOME/IP是一种支持远程通信的汽车/嵌入式通信协议 。支持远程过程调用&#xff08;RPC…

游戏引擎学习第89天

回顾 由于一直没有渲染器&#xff0c;终于决定开始动手做一个渲染器&#xff0c;虽然开始时并不确定该如何进行&#xff0c;但一旦开始做&#xff0c;发现这其实是正确的决定。因此&#xff0c;接下来可能会花一到两周的时间来编写渲染器&#xff0c;甚至可能更长时间&#xf…

PostgreSql-COALESCE函数、NULLIF函数、NVL函数使用

COALESCE函数 COALESCE函数是返回参数中的第一个非null的值&#xff0c;它要求参数中至少有一个是非null的; select coalesce(1,null,2),coalesce(null,2,1),coalesce(null,null,null); NULLIF(ex1,ex2)函数 如果ex1与ex2相等则返回Null&#xff0c;不相等返回第一个表达式的值…

【苍穹外卖 Day1】前后端搭建 Swagger导入接口文档

项目技术选型 前端 直接使用打包好的nginx运行。 后端 1、导入初始代码结构如下&#xff1a; 2、将代码上传远程仓库。 3、创建数据库&#xff0c;并修改数据库配置。 4、断点调试&#xff0c;前后端联调。 5、使用Nginx代理&#xff0c;修改Nginx配置 好处&#xff1a;提…