高效简洁!React 开发时用到的 11 种设计模式

284 阅读17分钟

来源:《高效简洁!React 开发时用到的 11 种设计模式》晨米酱-语雀文档

作为一名React开发者,你可能深知构建用户界面是一件令人兴奋且有趣的事情。但随着项目规模的增长,代码可能会变得混乱且难以维护。这时候,React 设计模式就派上用场了!

在本文中,我们将介绍 11 种重要的设计模式,它们能让你的React代码:

  • 更加整洁
  • 运行更高效
  • 更易于理解

掌握设计模式是成为高级前端工程师的重要一步。

在深入了解这些模式之前,让我们先来理解什么是设计模式,以及为什么要关注它们。

什么是编程中的设计模式?

设计模式是针对常见编程问题的经过验证的解决方案。

设计模式是经过实践验证的、用于解决常见编程问题的方法。与其在编写代码时重复造轮子,不如利用设计模式来高效、可靠地解决问题。你可以将其视为代码的架构蓝图。

设计模式并非简单的代码模板,而是改进工作方式和结构的思路。它们帮助开发者更好地组织项目,并避免常见的陷阱。

为什么要在React中使用设计模式?

使用设计模式非常重要,因为它们能:

  1. 提高代码可读性:清晰的模式意味着其他开发者(或未来的你)能快速理解代码。
  2. 减少Bug:结构化的代码降低了出错的风险。
  3. 提升效率:避免重复解决相同问题。
  4. 改善协作:共享的模式让团队协作更高效。
  5. 增强扩展性:当应用程序规模扩大时,设计模式有助于保持代码的有序性。

你可以将设计模式作为代码质量的基准。

了解设计模式的重要性后,接下来我们来看看你应该掌握的11种React设计模式!

11 种 React 设计模式

设计模式 #1:容器组件与展示组件

该模式将应用程序的逻辑(容器)与UI展示(展示组件)分离,提升代码结构性,使每个部分更易于管理。

容器组件与展示组件的定义

  • 容器组件处理逻辑和数据获取,不关心UI呈现。
  • 展示组件专注于UI展示,通过props接收数据并渲染。

目的

该模式的目标是关注点分离:容器组件负责逻辑,展示组件负责UI,增强代码的可理解性、可测试性和可维护性。

使用技巧

  • 保持展示组件简单:它们仅关注显示数据,不涉及数据源。
  • 提高UI复用性:展示组件与逻辑解耦,可在不同地方重用。

优缺点

优点 ✅缺点 ❌
清晰分离逻辑与UI可能增加文件和组件数量
便于单独测试容器和UI组件对简单应用可能显得过度设计
促进UI组件复用

最适用场景

  • 中大型应用
  • 复杂数据获取的项目

代码示例

展示组件(只负责显示数据)

// UserList.jsx

const UserList = ({ users }) => (
  <ul>
    {users.map(user => (
      <li key={user.id}>{user.name}</li>

    ))}
  </ul>

);

export default UserList;

容器组件(负责数据获取)

// UserListContainer.jsx

import { useEffect, useState } from 'react';
import UserList from './UserList';

const UserListContainer = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(setUsers);
  }, []);

  return <UserList users={users} />;
};

export default UserListContainer;

设计模式 #2:自定义 Hooks

自定义 Hooks 使得在 React 组件中提取和重用状态逻辑成为可能。通过将逻辑封装成可复用的函数,可以避免在多个组件中重复相同的代码。

为什么使用自定义 Hooks

当多个组件共享相同的逻辑(如数据获取、表单处理等)时,自定义 Hooks 让这些逻辑抽象并重复使用。

命名约定

自定义 Hooks 以 use 开头,遵循 React 内置 Hooks(如 useStateuseEffect)的命名约定。

示例:useDataFetch()

目的

自定义 Hooks 的目的是通过重用状态逻辑实现代码的 DRY(Don't Repeat Yourself)原则,从而保持组件简洁、专注、易于理解。

使用技巧

  • 保持专注:自定义 Hooks 应解决特定问题(如数据获取、表单处理)
  • 返回必要内容:只返回组件真正需要的数据和函数
  • 内部使用其他 Hooks:自定义 Hooks 可以调用 React 内置的 useStateuseEffect,甚至其他自定义 Hooks

优缺点

优点 ✅缺点 ❌
减少代码重复过度使用可能使代码难以理解
组件保持简洁专注
易于测试和重用

最适用场景

  • 需要涉及状态或副作用的可重用逻辑
  • 数据获取、身份验证、表单处理等场景

代码示例

// useFetch.js
import { useState, useEffect } from 'react';

const useFetch = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch(url)
      .then((res) => res.json())
      .then((data) => {
        setData(data);
        setLoading(false);
      })
      .catch((error) => {
        setError(error);
        setLoading(false);
      });
  }, [url]);

  return { data, loading, error };
};

export default useFetch;

// 使用自定义 Hook 的组件

import useFetch from './useFetch';

const UserList = () => {
  const { data: users, loading, error } = useFetch('https://api.example.com/users');

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

export default UserList;

你注意到了吗?在这个代码示例中,我们也使用了设计模式 #1:容器组件和展示组件。

什么时候不应该使用自定义 Hooks

  • 某个组件特定的逻辑且难以复用时
  • 当引入过多抽象会让代码变得难以理解时
  • 如果需要重用 JSX,创建一个组件
  • 如果需要重用不涉及 React hooks 的逻辑,创建一个工具函数
  • 如果需要重用包含 React hooks 的逻辑,创建一个自定义 Hook

设计模式 #3:复合组件

在 React 中,复合组件是一种设计模式,其中一个组件由多个协同工作的小组件组成。这种模式的理念是创建一个灵活且可重用的组件系统,其中每个子组件具有特定功能,并共同构成一个完整的组件。这就像构建一套设计好的乐高积木,每个积木都有特定功能,但可以灵活组合成各种结构。

现实生活示例

一个典型的复合组件是 <BlogCard>,它通常包含标题、描述、图片和“阅读更多”内容的按钮等子元素。由于博客由多个页面组成,你可能需要根据不同场景以不同方式展示 <BlogCard>

例如,在搜索结果页面中你可能会选择隐藏图片,而在其他页面中则可能将图片放在标题上方。虽然可以通过 props 和条件渲染来实现,但如果这些定制逻辑过多,代码会变得冗长且难以维护。这时复合组件模式就能派上用场。

使用场景

  • 标签页
  • 下拉菜单
  • 手风琴组件
  • 博客和产品卡片

目的

复合组件模式的核心是保持共享状态和行为的同时,提供灵活的组合 UI 元素的能力。

使用技巧

  • 父组件管理状态:父组件应管理共享状态,确保各个子组件之间的数据一致性
  • 使用 Context 传递状态:对于深层嵌套的组件,React Context 可以简化状态的传递和管理

优缺点

优点 ✅缺点 ❌
灵活的组件组合方式对初学者来说可能较复杂
保持组件的封装性深层嵌套时较难理解

最适用场景

  • 标签页、手风琴、下拉菜单和卡片等 UI 模式
  • 需要在多个子组件之间共享状态

代码示例

// ProductCard.jsx

export default function ProductCard({ children }) {
  return (
    <div className='product-card'>{children}</div>

  );
}

ProductCard.Title = ({ title }) => {
  return <h2 className='product-title'>{title}</h2>;
};
ProductCard.Image = ({ imageSrc }) => {
  return <img className='product-image' src={imageSrc} alt='Product' />;
};
ProductCard.Price = ({ price }) => {
  return <p className='product-price'>${price}</p>;
};

ProductCard.Title.displayName = 'ProductCard.Title';
ProductCard.Image.displayName = 'ProductCard.Image';
ProductCard.Price.displayName = 'ProductCard.Price';
// App.jsx

import ProductCard from './components/ProductCard';

export default function App() {
  return (
    <ProductCard>
      <ProductCard.Image imageSrc='https://via.placeholder.com/150' />
      <ProductCard.Title title='产品标题' />
      <ProductCard.Price price='9.99' />
    </ProductCard>

  );
}

你可以按任意顺序排列内部组件,灵活定制展示效果。

设计模式 #4:属性组合

属性组合模式允许通过传递不同的 props 组合来修改组件的行为或外观,从而创建组件的变体,而无需重复编写多个版本的组件。

该模式增强了灵活性和定制化,避免了代码库中出现过多相似的组件。

常见使用场景

  • 具有不同样式的按钮(如:primary, secondary, disabled
  • 带有可选元素的卡片(如:图片、图标或标题)

目的

属性组合模式的目的是通过简单的方式创建组件的变体,而无需复制代码,使组件保持整洁、易于维护。

使用技巧

  • 组合Boolean props:对于简单变体,使用布尔型 props(如:isPrimaryisDisabled
  • 避免过多 props:如果需要过多 props 来控制行为,考虑拆分组件或使用复合组件模式(设计模式 #3)。

使用默认 Props:为 props 设置默认值,优雅处理缺失的 props。

优缺点

优点 ✅缺点 ❌
减少创建多个相似组件的需求过度使用可能导致"props 爆炸"
易于定制组件的行为和外观
保持代码 DRY(不重复自己)

最适用场景

  • 按钮、卡片、提示框等组件
  • 具有多个可配置状态的组件

代码示例

假设你要构建一个可以在样式、大小和禁用状态上变化的按钮组件:

// Button.jsx

const Button = ({ type = 'primary', size = 'medium', disabled = false, children, onClick }) => {
  let className = `btn ${type} ${size}`;
  if (disabled) className += ' disabled';

  return (
    <button className={className} onClick={onClick} disabled={disabled}>
      {children}
    </button>

  );
};
// App.jsx

import Button from './components/Button';

const App = () => (
  <div>
    <Button type="primary" size="large" onClick={() => alert('主要按钮')}>
      主要按钮
    </Button>

    <Button type="secondary" size="small" disabled>
      禁用的次要按钮
    </Button>

    <Button type="danger" size="medium">
      危险按钮
    </Button>

  </div>

);

设计模式 #5:受控组件

受控输入是指其值由 React 状态控制的表单元素。在这种模式中,表单输入的值始终与组件的状态保持同步,使 React 成为输入数据的唯一数据源。

该模式常用于输入框、文本区域、复选框和选择框等表单元素。

输入元素的值与 React 的状态绑定。当状态发生变化时,输入框的值会相应更新。

受控组件 VS 非受控组件:

  • 受控组件:值由 React 状态控制
  • 非受控组件:依赖 DOM 来管理其状态(例如,通过 ref 访问)

目的

受控组件的目的是完全控制表单输入,确保组件行为可预测且一致,特别适用于需要验证输入、应用格式化或动态提交数据的场景。

使用技巧

  • 使用 onChange 事件:通过 onChange 更新状态,确保输入值与状态同步
  • 初始化状态:设置初始状态,避免出现未定义值
  • 表单验证:利用受控组件实现实时验证或格式化

优缺点

优点 ✅缺点 ❌
易于验证和处理输入可能需要更多的样板代码
使表单元素可预测且易于调试在处理大型表单时可能影响性能
完全控制用户输入

最适用场景

  • 需要验证的表单:如实时验证输入字段时
  • 动态表单:表单输入依赖于动态数据或逻辑时
  • 复杂输入:例如格式化电话号码或电子邮件时

代码示例

import { useState } from 'react';

function MyForm() {
  const [name, setName] = useState('');

  const handleChange = (e) => {
    setName(e.target.value);
  };

  return (
    <form>
      <input 
        type="text" 
        value={name} 
        onChange={handleChange} 
      />
      <p>你的名字是:{name}</p>
    </form>
  );
}

设计模式 #6:错误边界

错误边界是 React 组件,用于捕获子组件树在渲染、生命周期方法和事件处理器的 JavaScript 错误。它可以防止应用崩溃,并显示一个优雅的回退 UI(fallback ui)来处理错误

这种模式对于提升 React 应用的健壮性和用户体验至关重要。

目的
错误边界的目的是防止某个组件出错时导致整个应用崩溃,相反,以显示用户友好的回退 UI,确保应用的其余部分继续正常运行。

使用技巧

  • 包裹关键组件:在可能失败的组件周围使用错误边界(如第三方集成组件)。
  • 记录错误:将错误日志发送至 Sentry 或 LogRocket 等服务以便调试。
  • 设计回退 UI:创建清晰的回退 UI,告知用户发生了问题。

优点和缺点

优点 ✅缺点 ❌
防止整个应用崩溃无法捕获事件处理器或异步代码中的错误
提供回退 UI 以获得更好的用户体验
帮助捕获和记录生产环境中的错误

最适用场景

  • 大型应用:单个组件的错误不应影响整个应用。
  • 第三方集成:嵌入的第三方组件可能出现不可预见的错误。
  • 复杂 UI 组件:用于动态内容或复杂渲染逻辑的组件。

代码示例

React 本身提供了错误边界的实现,但由于其使用类组件,稍显过时。推荐使用专门的 npm 库:react-error-boundary

// App.jsx

import { ErrorBoundary } from "react-error-boundary";

<ErrorBoundary fallback={<div>出现了一些问题</div>}>
  <App />
</ErrorBoundary>

设计模式 #7:懒加载(代码分割)

懒加载是一种只在需要时加载应用组件或部分内容的技术,与一次性加载所有内容不同。它将代码分割成更小的块,按需加载,从而减少初始加载时间,提升性能

在 React 中的实现

React 通过 React.lazy()Suspense 实现懒加载:

  1. React.lazy() :动态导入组件。
  2. Suspense:包装懒加载的组件,在组件加载时显示一个回退 UI(如加载动画)。

目的

懒加载通过减少初始包大小来优化应用性能,尤其适用于不需要立即加载所有组件的大型应用,带来更快的加载速度。

使用技巧

  • 分割路由:对路由使用懒加载,只加载每个页面所需的组件。
  • 与错误边界结合:将懒加载与错误边界结合使用,处理加载失败的情况(参见“设计模式 #6:错误边界”)。

优缺点

优点 ✅缺点 ❌
减少初始加载时间组件加载时会有延迟
提升大型应用性能需要处理加载状态和错误
按需加载,节省带宽过度分割代码会增加复杂性

最适用场景

  • 大型应用:适用于不需要立即加载所有组件的场景。
  • 性能关键页面:如仪表板等需要快速加载的页面。
  • 第三方组件:可懒加载的第三方组件集成。

代码示例

// Profile.jsx

const Profile = () => {
  return <h2>这是个人资料组件!</h2>;
};

export default Profile;
// App.jsx

import { Suspense, lazy } from 'react';

// 懒加载 Profile 组件
const Profile = lazy(() => import('./Profile'));

function App() {
  return (
    <div>
      <h1>欢迎使用我的应用</h1>

      {/* Suspense 提供加载状态 UI */}
      <Suspense fallback={<div>加载中...</div>}>
        <Profile />
      </Suspense>

    </div>

  );
}

export default App;

设计模式 #8:高阶组件(HOC)

高阶组件 接收一个组件作为参数,并返回一个注入了额外数据或功能的增强组件。

HOC 常用于逻辑复用,如身份验证、数据获取或样式注入等

HOC 的签名

const EnhancedComponent = withSomething(WrappedComponent);
  • WrappedComponent:被增强的源组件
  • EnhancedComponent:HOC 返回的新组件

命名约定

HOC 通常使用 with 前缀命名,例如 withAuthwithLoggingwithLoading

使用技巧

  • 保持纯函数:HOC 应保持纯函数,不修改 WrappedComponent
  • 传递 props:始终将 props 传递给 WrappedComponent,确保它能接收所需的所有属性。

优缺点

优点 ✅缺点 ❌
促进代码复用可能导致“包装地狱”(过多嵌套的 HOC)
保持组件的单一职责多层抽象可能使调试变得复杂

最适用场景

  • 横切关注点:如认证、日志记录、主题等共享逻辑。
  • 可复用增强:多个组件需要相同行为时。
  • 复杂应用:需要抽象公共逻辑,提升可读性。

代码示例

以下是为组件添加加载状态的 HOC 示例:

// HOC - withLoading.js

const withLoading = (WrappedComponent) => {
  return ({ isLoading, ...props }) => {
    if (isLoading) {
      return <div>加载中...</div>;
    }
    return <WrappedComponent {...props} />;
  };
};

export default withLoading;
// DataComponent.js

const DataComponent = ({ data }) => {
  return <div>数据: {data}</div>;
};

export default DataComponent;
// App.js

import { useState, useEffect } from 'react';
import withLoading from './withLoading';
import DataComponent from './DataComponent';

// 使用 HOC 增强组件
const DataComponentWithLoading = withLoading(DataComponent);

const App = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setTimeout(() => {
      setData('这是数据!');
      setLoading(false);
    }, 2000);
  }, []);

  return (
    <div>
      <h1>我的应用</h1>

      <DataComponentWithLoading isLoading={loading} data={data} />
    </div>

  );
};

export default App;

设计模式 #9:使用 Reducer 进行状态管理

当应用状态变得复杂时,使用 Reducer 替代 useState 更为高效。Reducer 通过可预测和有组织的方式处理状态更新。

Reducer 本质上是一个函数,接收当前状态和动作,返回新的状态。

基础概念

  • Reducer 函数:纯函数,接收状态和动作作为参数,并返回新状态。
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
};
  • Action(动作) :描述状态更新的对象,通常包含 type 字段及其他数据(payload)。
  • Dispatch(分发) :用于向 reducer 发送动作以触发状态更新。

目的

当状态逻辑变得过于复杂而不适合使用 useState 时,使用 reducer 集中管理状态更新,使代码更易维护、调试和扩展。

使用技巧

  • 保持 Reducer 纯净:避免副作用(如 API 调用或异步代码)。
  • 使用常量定义动作类型:避免拼写错误。

优缺点

优点 ✅缺点 ❌
简化复杂状态逻辑增加样板代码(动作、分发等)
集中状态更新便于调试对简单状态管理可能过度设计
使状态转换可预测学习曲线较陡,特别对初学者

最适用场景

  • 复杂状态逻辑:当状态依赖多个条件时。
  • 中大型应用:多个组件共享状态的应用。

代码示例

以下是使用 useReducer 管理状态的计数器示例:

import { useReducer } from 'react';

// 第一步:定义 reducer 函数
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    case 'RESET':
      return { count: 0 };
    default:
      return state;
  }
};

// 第二步:定义初始状态
const initialState = { count: 0 };

// 第三步:创建组件
const Counter = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <h1>计数: {state.count}</h1>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>增加</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>减少</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>重置</button>
    </div>
  );
};

export default Counter;

在现代 React 开发中,Redux是基于 reducer 最常用的状态管理库。

设计模式 #10:使用 Provider 进行数据管理(Context API)

Provider 模式在数据管理中非常有用,通过 Context API 在组件树中传递数据,有效解决了 React 开发中常见的 props 层层传递问题。

Provider 允许管理全局状态,并且任何需要状态的组件都可访问。这种模式通过向组件树“提供”数据,避免了 属性传取(通过多层组件传递 props)。

基础概念

  • Context:React 提供的特性,用于创建和共享全局状态。
  • Provider:为子树中的组件提供数据的组件。
  • Consumer:消费 Provider 提供的数据的组件。
  • useContext Hook:无需 Consumer,直接访问 context 值。

目的

这种模式通过 Provider 提供全局状态,简化了深层嵌套组件之间的数据共享。它有助于保持代码整洁、可读,并避免不必要的 props 传递。

使用技巧

  • 合理使用 Context:适合用于主题、认证或用户设置等全局状态。
  • 结合 Reducer:对于复杂状态逻辑,结合 useReducer 使用,获得更好的控制。
  • 拆分 Context:避免使用单一的大 context,针对不同数据使用多个小 context。

优点与缺点

优点 ✅缺点 ❌
减少 props 层层传递不适合频繁变化的数据(可能导致不必要的重渲染)
集中数据便于访问如果 context 值频繁变化,可能影响性能
对中小型应用易于设置

最适用场景

  • 全局状态管理:如主题、认证状态、语言设置等。
  • 避免 Props 层层传递:当数据需要通过多层组件传递时。
  • 中等复杂度应用:适合需要简单全局状态管理的应用。

代码示例

以下是使用 ThemeProvider 进行数据管理的示例:

// ThemeContext.jsx

import { createContext, useState } from 'react';

export const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>

  );
};
// ThemeToggleButton.jsx

import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

const ThemeToggleButton = () => {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button onClick={toggleTheme}>
      切换到{theme === 'light' ? '深色' : '浅色'}模式
    </button>

  );
};

export default ThemeToggleButton;
// App.js

import { ThemeProvider } from './ThemeContext';
import ThemeToggleButton from './ThemeToggleButton';

const App = () => {
  return (
    <ThemeProvider>
      <div>
        <h1>欢迎使用应用</h1>

        <ThemeToggleButton />
      </div>

    </ThemeProvider>

  );
};

export default App;

在 React 19 中,你不需要使用 <Context.Provider>,可以直接将 <Context> 作为 provider 渲染

const ThemeContext = createContext('');

function App({children}) {
  return (
    <ThemeContext value="dark">
      {children}
    </ThemeContext>

  );  
}

设计模式 #11:Portals

Portals 允许你将子元素渲染父组件层级之外的 DOM 树部分。

这对于渲染需要脱离正常 DOM 流显示的元素(如模态框、工具提示或覆盖层)非常有用。

即使 DOM 父级发生变化,React 组件结构保持不变。

目的

此模式的目的是提供一种在父组件层级之外渲染组件的方法,使得需要打破正常流的 UI 元素易于管理,同时不破坏组件树的主要结构。

使用技巧

  • 模态框和覆盖层:Portals 适合用于渲染显示在其他内容之上的元素,如模态框和工具提示。
  • 避免过度使用:虽然非常有用,Portals 应该只在必要时使用,因为它可能使组件层级和事件传播变得复杂。

优点与缺点

优点 ✅缺点 ❌
保持组件树整洁,避免布局问题可能使事件传播变复杂(如点击事件不再冒泡)

最适用场景

  • 覆盖层:模态框、工具提示或任何需要显示在其他内容之上的 UI 元素。
  • 打破 DOM 流:当元素需要脱离标准组件层级时(如通知)。

代码示例

// Modal.jsx

import { useEffect } from 'react';
import ReactDOM from 'react-dom';

const Modal = ({ isOpen, closeModal, children }) => {
  // 阻止 body 滚动
  useEffect(() => {
    if (isOpen) {
      document.body.style.overflow = 'hidden';
    }
    return () => {
      document.body.style.overflow = 'unset';
    };
  }, [isOpen]);

  if (!isOpen) return null;

  return ReactDOM.createPortal(
    <>
      {/* 遮罩层 */}
      <div 
        style={overlayStyles} 
        onClick={closeModal}
      />
      {/* 模态框 */}
      <div style={modalStyles}>
        {children}
        <button onClick={closeModal}>关闭</button>
      </div>
    </>,
    document.getElementById('modal-root')
  );
};

const overlayStyles = { /* 样式 */ };
const modalStyles = { /* 样式 */ };

export default Modal;
// App.js

import { useState } from 'react';
import Modal from './Modal';

const App = () => {
  const [isModalOpen, setIsModalOpen] = useState(false);
  
  return (
    <div>
      <h1>React Portals 示例</h1>
      <button onClick={() => setIsModalOpen(true)}>打开模态框</button>
      <Modal isOpen={isModalOpen} closeModal={() => setIsModalOpen(false)}>
        <h2>模态框内容</h2>
        <p>这是模态框的内容</p>
      </Modal>
    </div>

  );
};

export default App;
// index.html

<body>
  <div id="root"></div>
  <div id="modal-root"></div>
</body>

总结

学习和掌握设计模式是成为高级前端开发者的关键步骤。🆙

这些模式不仅仅是理论,它们解决了状态管理、性能优化和 UI 组件架构等实际问题。

通过在日常工作中应用这些模式,你将能够应对各种开发挑战,创建高性能且易于维护的应用。