Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >浅析 5 种 React 组件设计模式

浅析 5 种 React 组件设计模式

作者头像
政采云前端团队
发布于 2024-01-11 06:49:09
发布于 2024-01-11 06:49:09
73100
代码可运行
举报
文章被收录于专栏:采云轩采云轩
运行总次数:0
代码可运行

作为一名 React 开发者,你可能会面临下面几个问题:

  • 如何构建一个高复用度性的组件,使其适应不同的业务场景?
  • 如何构建一个具有简单 API的组件,使其易于使用?
  • 如何构建一个在 UI 和功能方面具有可扩展性的组件?

为解决上述问题,下面介绍五种 React 组件设计模式,并对比它们的优缺点。

1. 复合组件模式

复合组件模式是一种通过将多个简单组件组合在一起创建更复杂组件的方法。这种模式使得组件的逻辑分离,每个简单组件负责特定的功能。通过复合组件,可以轻松构建可复用的、功能完备的组件。

如果想要设计一个定制化程度高,API方便理解的组件,可以考虑这个模式,这种模式不会出现多层Props传递的情况。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React, { useState } from 'react';

// 基础按纽组件
const Button = ({ label, onClick }) => (
  <button onClick={onClick}>{label}</button>
);

// 基础文本组件
const TextBox = ({ value, onChange }) => (
  <input type="text" value={value} onChange={onChange} />
);

// 复合组件
const LoginPanel = () => {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');

  const handleLogin = () => {
    // 实现登录逻辑
    console.log(`Logging in with ${username} and ${password}`);
  };

  return (
    <div className="login-panel">
      <TextBox value={username} onChange={(e) => setUsername(e.target.value)} />
      <TextBox value={password} onChange={(e) => setPassword(e.target.value)} />
      <Button label="Login" onClick={handleLogin} />
    </div>
  );
};

// 使用示例
const App = () => {
  return (
    <LoginPanel />
  );
};

export default App;

在这个例子中,LoginPanel 是一个复合组件,它包含了两个基本组件 TextBox 和一个带有登录逻辑的 Button。

优点:

  • API 复杂度降低: 避免将Props全部塞入一个容器组件中,而是直接将Props传递给相对应的子组件。
  • 高度可复用性: 基础组件可以在多个场景中重复使用。
  • 逻辑分离: 每个基础组件专注于一项任务。
  • 组件数量增多: 随着组件层级的增加,将会增加JSX的行数,并且代码可能变得复杂。
  • 不适用于所有场景: 对于简单的场景,引入复合组件模式可能会显得繁琐和不必要。

适用场景:

  • 表单和表单域: 当设计表单时,可以使用复合式组件将整个表单拆分成多个表单域组件,每个表单域负责处理特定的输入或验证逻辑。这样可以更好地组织表单逻辑,提高可维护性。
  • 对话框和模态框: 对话框或模态框通常包含标题、内容和操作按钮。可以使用复合式组件将这些部分拆分成独立的组件,以便在应用中以不同方式重复使用。

2. 受控组件模式

受控组件模式就是将组件转换为受控组件,通过直接修改 Props 影响组件内部的状态,一般在表单组件中比较常用。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React, { useState } from 'react';

const Button = ({ label, onClick }) => (
  <button onClick={onClick}>{label}</button>
);

const TextBox = ({ value, onChange }) => (
  <input type="text" value={value} onChange={onChange} />
);

// 受控组件模式的复合组件
const ControlledLoginPanel = () => {
  const [loginData, setLoginData] = useState({ username: '', password: '' });

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setLoginData((prevData) => ({
      ...prevData,
      [name]: value,
    }));
  };

  const handleLogin = () => {
    // 实现登录逻辑
    console.log(`Logging in with ${loginData.username} and ${loginData.password}`);
  };

  return (
    <div className="login-panel">
      <TextBox
        value={loginData.username}
        onChange={handleInputChange}
      />
      <TextBox
        value={loginData.password}
        placeholder="Password"
      />
      <Button label="Login" onClick={handleLogin} />
    </div>
  );
};

// 使用示例
const App = () => {
  return (
    <ControlledLoginPanel />
  );
};

export default App;

在这个例子中,ControlledLoginPanel 组件就是一个受控组件的例子,其中的输入框的值由 React 状态管理。

优点:

  • 提供更多的控制: 将内部的状态暴露在组件之外,允许用户通过控制它,而直接影响组件。
  • 一致性和可预测性: React 组件的状态是单一数据源,使得应用的状态变得更加可预测和一致。状态的变化完全由 React 控制,减少了意外的行为。 缺点:
  • 繁琐的代码: 受控组件相对于非受控组件来说,需要更多的代码。每个输入框都需要设置对应的状态和事件处理函数,这可能导致代码量的增加。
  • 性能开销: 受控组件每次输入变化都会触发状态更新,可能导致频繁的重新渲染。对于大型或性能敏感的应用,这可能带来一些性能开销。
  • 不适用于所有场景:受控组件更适用于表单交互比较复杂,需要实时验证或涉及多个输入字段之间关系的场景。对于简单的表单,可能显得有些繁重。

适用场景:

  • 动态表单元素: 在需要动态添加或删除表单元素的情况下,受控组件模式可以很容易地实现。通过使用数组来保存表单元素的状态,可以动态渲染和更新表单。
  • 模态框控制: 当需要通过 props 控制模态框的显示或隐藏状态时,可以使用受控组件模式。

3. 自定义 Hooks 模式

自定义Hooks模式是一种将组件逻辑提取为可重用的函数的方法。将主要的逻辑转移到一个Hooks中。用户可以访问这个Hooks,并公开了几个内部逻辑(状态、处理程序) ,使用户能够更好地控制组件。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React, { useState } from 'react';

const Button = ({ label, onClick }) => (
  <button onClick={onClick}>{label}</button>
);

const TextBox = ({ value, onChange, placeholder }) => (
  <input type="text" value={value} onChange={onChange} placeholder={placeholder} />
);

// 自定义 Hook,处理登录表单逻辑
const useLoginForm = () => {
  const [loginData, setLoginData] = useState({ username: '', password: '' });

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setLoginData((prevData) => ({
      ...prevData,
      [name]: value,
    }));
  };

  const handleLogin = () => {
    // 在这里实现登录逻辑
    console.log(`使用用户名 ${loginData.username} 和密码 ${loginData.password} 登录`);
  };

  return {
    loginData,
    handleInputChange,
    handleLogin,
  };
};

// 在组件中使用自定义 Hook
const ControlledLoginPanel = () => {
  const { loginData, handleInputChange, handleLogin } = useLoginForm();

  return (
    <div className="login-panel">
      <TextBox
        value={loginData.username}
        onChange={handleInputChange}
        placeholder="用户名"
      />
      <TextBox
        value={loginData.password}
        onChange={handleInputChange}
        placeholder="密码"
      />
      <Button label="登录" onClick={handleLogin} />
    </div>
  );
};

// 使用示例
const App = () => {
  return (
    <ControlledLoginPanel />
  );
};

export default App;

在这个例子中,我们将与登录表单相关的状态和逻辑抽离到一个自定义 useLoginForm Hook 中。使得 ControlledLoginPanel 组件更专注于渲染 UI,减少了状态和事件处理逻辑的混杂。

优点:

  • 逻辑重用: 将逻辑提取为 Hooks,可以在多个组件中重用。
  • 组件更简洁: 组件代码更加清晰,只关注与 UI 相关的逻辑。 缺点:
  • 实现复杂度变高: 逻辑部分与渲染部分分开,需要将两者结合起来才能很好的理解组件工作原理。 适用场景:
  • 数据获取和处理逻辑: 将数据获取和处理逻辑提取到自定义 Hook 中,可以在多个组件之间共享相同的数据逻辑。
  • 副作用的封装: 当有需要在组件中处理副作用的情况,可以将副作用逻辑封装到自定义 Hook 中,以提高可维护性。

4. Props Getters 模式

模式 3 中的自定义Hooks提供了很好的控制方式;但是比较难以集成,使用者需要按照组件提供的HooksState相结合进行编写逻辑,提高了集成的复杂度。Props Getters模式则是通过简化这一过程来实现。Getter是一个返回多个属性的函数,它具有有意义的名称,使得开发者能够清楚地知道哪个Getter对应于哪个JSX元素。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React, { useState } from 'react';

const Button = ({ getLabel, handleClick }) => (
  <button onClick={handleClick}>{getLabel()}</button>
);

const TextBox = ({ getValue, onChange, placeholder }) => (
  <input type="text" value={getValue()} onChange={onChange} placeholder={placeholder} />
);

const ControlledLoginPanel = ({ getUsernameProps, getPasswordProps, handleLogin }) => {
  return (
    <div className="login-panel">
      <TextBox {...getUsernameProps()} placeholder="Username" />
      <TextBox {...getPasswordProps()} placeholder="Password" />
      <Button getLabel={() => 'Login'} handleClick={handleLogin} />
    </div>
  );
};

// 使用 Props Getters 模式的 Hooks
const useLoginForm = () => {
  const [loginData, setLoginData] = useState({ username: '', password: '' });

  const handleInputChange = (name) => (e) => {
    const { value } = e.target;
    setLoginData((prevData) => ({
      ...prevData,
      [name]: value,
    }));
  };

  const handleLogin = () => {
    // 实现登录逻辑
    console.log(`Logging in with ${loginData.username} and ${loginData.password}`);
  };

  const getUsernameProps = () => ({
    getValue: () => loginData.username,
    onChange: handleInputChange('username'),
  });

  const getPasswordProps = () => ({
    getValue: () => loginData.password,
    onChange: handleInputChange('password'),
  });

  return {
    getUsernameProps,
    getPasswordProps,
    handleLogin,
  };
};

// 使用示例
const App = () => {
  const { getUsernameProps, getPasswordProps, handleLogin } = useLoginForm();

  return (
    <ControlledLoginPanel
      getUsernameProps={getUsernameProps}
      getPasswordProps={getPasswordProps}
      handleLogin={handleLogin}
    />
  );
};

export default App;

在这个例子中,我们基于模式 3 进行了改造,把 ControlledLoginPanel 组件需要的 Props 通过函数的方式进行获取,以实现更灵活、更简便的组件复用。

优点:

  • 易用性: 开发人员只需要将 Getter传入到正确的 JSX元素即可。
  • 组件关注点分离: 组件通过 props 获取所需的属性,使组件关注点更为分离,组件本身不处理状态和逻辑,提高了组件的可维护性。
  • 减少嵌套层级: 相较于 Hooks 模式,Props Getters 模式可能减少了一些嵌套,使得组件结构更加扁平。

缺点:

  • 缺乏可见性: Getter 带来了抽象,使组件更容易集成,但也更为黑盒。
  • 引入更多回调函数: 使用 Props Getters 模式可能引入更多的回调函数,一些开发者可能认为这会使代码显得更加复杂。
  • 依赖外部 API: Props Getters 模式依赖外部传递的回调函数,可能导致一些依赖关系,不够自包含。

适用场景:

  • 数据过滤: 在一个数据展示组件中,通过 Props Getters 模式可以将数据过滤逻辑提取出来,允许外部根据特定条件获取过滤后的数据。
  • 表单验证: 在一个表单组件中,通过 Props Getters 模式可以将表单验证的逻辑从组件中抽离,允许外部调用表单组件的验证函数,并获取验证结果。

5. State Reducer 模式

State Reducer 模式是一种通过将组件的状态更新逻辑委托给一个函数,实现更灵活的状态管理方式。这种模式通常在处理复杂的状态逻辑时非常有用。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React, { useState } from 'react';

const TextInput = ({ getInputProps }) => {
  const inputProps = getInputProps();

  return <input {...inputProps} />;
};

const StateReducerExample = () => {
  // 初始状态为一个空字符串
  const [inputValue, setInputValue] = useState('');

  // stateReducer 函数用于处理状态的变化
  const stateReducer = (state, changes) => {
    // 使用 switch case 处理不同的状态变化情况
    switch (Object.keys(changes)[0]) {
      // 如果变化的是 value 属性
      case 'value':
        // 如果输入的字符数量超过 10 个,则不允许变化
        if (changes.value.length > 10) {
          return state;
        }
        break;
      // 可以添加其他 case 处理不同的状态变化
      default:
        break;
    }
    // 返回新的状态
    return { ...state, ...changes };
  };

  // 获取传递给子组件的 props
  const getInputProps = () => {
    return {
      value: inputValue,
      // 在输入框变化时调用 stateReducer 处理状态变化
      onChange: (e) => setInputValue(stateReducer({ value: e.target.value })),
    };
  };

  return (
    <div>
      <h3>State Reducer Example</h3>
      {/* 将获取的 props 传递给 TextInput 组件 */}
      <TextInput getInputProps={getInputProps} />
    </div>
  );
};

export default StateReducerExample;

在这个例子中,StateReducerExample 组件包含一个输入框,通过 getInputProps 函数将输入框的值和变化处理逻辑传递给 TextInput 组件。stateReducer 函数处理状态的变化,确保输入的字符数量不超过 10 个。

优点:

  • 状态管理灵活: 可以通过自定义的状态更新函数实现更复杂的状态管理逻辑。
  • 更好的组织代码: 将状态的处理逻辑集中在一个 stateReducer 函数中,可以使代码更有组织性,减少了在组件中处理状态的复杂性。
  • 清晰的状态更新逻辑: 通过 stateReducer 可以清晰地看到每个状态变化是如何被处理的,使得状态更新逻辑更易于理解。

缺点:

  • 增加复杂度: 引入 stateReducer 可能会使代码结构变得更加复杂,尤其是在处理多个状态变化情况时。这可能导致一些开发者对代码的理解产生困难。
  • 可能造成冗余代码: 在某些情况下,可能会因为需要为每个状态变化情况编写处理逻辑而导致一些冗余的代码,特别是在处理简单状态时。
  • 不适用于简单场景: 在简单场景下使用状态约减可能显得繁琐不必要。

适用场景:

  • 复杂状态管理: 当组件的状态比较复杂,有多个相关联的状态需要进行更新时,State Reducer 模式可以帮助将状态管理逻辑进行更细粒度的控制。
  • 异步状态更新: 当需要进行异步状态更新时,State Reducer 模式可以帮助处理异步回调,以确保状态正确更新。
  • 控制状态更新流程: 在某些场景下,需要更灵活地控制状态更新的流程,例如在某个条件下阻止状态更新或根据条件进行额外的处理。

结论

通过这 5 种 React 组件设计模式,我们对“控制度”和“复杂度”有了更清晰的认识,下图是复杂度和控制度的一个趋势图。

总体来说,设计的组件越灵活,功能也就越强大,复杂度也会更高。作为开发人员,建议大家根据自己的业务逻辑以及使用人群,灵活使用以上的设计模式。

参考文章

React 组件设计模式

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 政采云技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
10分钟,掌握C语言指针
说到指针,估计还是有很多小伙伴都还是云里雾里的,有点“知其然,而不知其所以然”。但是,不得不说,学了指针,C语言才能算是入门了。指针是C语言的「精华」,可以说,对对指针的掌握程度,「直接决定」了你C语言的编程能力。
刘盼
2020/11/03
1.5K0
10分钟,掌握C语言指针
指针(二)
同理,a[1]是a[ 1 ] [ 0 ]的地址,a[2]是a[ 2 ] [ 0 ]的地址
DeROy
2020/05/11
3610
【C语言】⒉万字带你玩转高阶指针『0»1』
🚀write in front🚀 ---- 🔎大家好,我是謓泽,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎 🏅2021年度博客之星物联网与嵌入式开发TOP5~2021博客之星Top100~阿里云专家^星级博主~掘金 || InfoQ创作者~周榜34»总榜2815🏅 🆔本文由 謓泽 原创 CSDN首发🙉如需转载还请通知⚠ 📝个人主页:打打酱油desuCSDN博客💬 🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​ 📣系列专栏:【C】系列_打打酱油desu-CSDN博客[〇~①]🎓
謓泽
2022/12/12
6170
【C语言】⒉万字带你玩转高阶指针『0»1』
详解C语言中的数组指针与指针数组
数组指针的意思即为通过指针引用数组,p先和*结合,说明了p是一个指针变量,指向一个大小为5的数组。所以,int (*p)[5]即为一个数组指针。int *p[5]则是一个大小为5且存放整型指针的数组。
全栈程序员站长
2022/09/19
3.7K0
详解C语言中的数组指针与指针数组
C语言·深入理解指针(进阶)
其实并不是,这行代码的本质只是将"Hello world"的首字符地址存放到了p当中。
AUGENSTERN_
2024/04/09
1400
C语言·深入理解指针(进阶)
拿捏指针(二)
前面我们已经讲了,C语言的第一篇《拿捏指针(一)》,接下里我们继续深入的来了解指针。
秋邱
2024/10/09
820
拿捏指针(二)
【重拾C语言】七、指针(二)指针与数组(用指针标识数组、多维数组与指针、数组指针与指针数组)
如果一个指针p指向数组a的首地址(即指向a[0]),则p与a表示的是同一个对象。
Qomolangma
2024/07/30
2530
【重拾C语言】七、指针(二)指针与数组(用指针标识数组、多维数组与指针、数组指针与指针数组)
精华篇:数组指针
数组指针,指的是数组名的指针,即数组首元素地址的指针。即是指向数组的指针。
全栈程序员站长
2022/09/06
3510
精华篇:数组指针
深入浅出C语言指针(进阶篇)
在C语言中,指针是至关重要的一部分,掌握指针的用法对于编写高效、简洁的代码具有极大帮助。本文将带您深入了解C语言指针的高级用法,助您迈向编程高手之路。
平凡之路.
2024/10/09
1420
深入浅出C语言指针(进阶篇)
C:指针学习-指针变量—学习笔记
这句代码的意思是什么呢?是把 "JonlyMay"字符串存放到指针变量p 中吗?
LonlyMay
2024/10/21
1030
C:指针学习-指针变量—学习笔记
指针进阶(1)
代码 const char* p = “abcdef”; 并不是把字符串 abcdef 放到字符指针 p 里去,而是把字符串 abcdef 首字符的地址放到 p 中去;同时,因为 abcdef 是一个常量字符串,所以要用 const 进行修饰,以保证 p 中的内容不会被修改。
waves浪游
2024/01/22
1340
指针进阶(1)
【C语言基础】:深入理解指针(三)
书山有路勤为径,学海无涯苦作舟。 创作不易,宝子们!如果这篇文章对你们有帮助的话,别忘了给个免费的赞哟~
爱喝兽奶的熊孩子
2024/04/10
1060
【C语言基础】:深入理解指针(三)
指针数组和数组指针的详细解答
/**首先弄明白什么是指针数组,什么又是数组指针。 指针数组: 类型名 *数组名[数组长度] 如: int *p[8]; 因为优先级的关系,p先与[]结合,说明p是一个数组,然后再与*结合数组p 的元素是指向整型数据的指针。相当于定义了8个整型指针变量。在此,p就是 数组元素为指针的数组,本质为数组。 数组指针: 类型名 (*指针名)[数组长度] 如:int (*p)[8]; p先与*号结合
谙忆
2021/01/20
5330
指针数组和数组指针的详细解答
C语言进阶-高阶指针
目录 前言 字符指针 指针数组 数组指针 &数组名VS数组名 数组指针的使用 数组参数、指针参数 一维数组传参 二维数组传参 一级指针传参 二级指针传参 函数指针 函数指针数组 指向函数指针数组的指针 ---- 前言 对于初阶指针内容在我的另一篇文章中已经写到(有想法的可以去看看) 附上链接:C语言初阶指针 回顾: 1. 指针是地址,指针变量是用来存放地址的变量(唯一标识一块内存空间)  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)  3. 指针的类型决定了指针的+-整数的步长,指
用户9645905
2022/11/30
4160
C语言进阶-高阶指针
【C语言进阶】指针数组 —— 数组指针
🎬 鸽芷咕:个人主页 🔥 个人专栏:《C语言进阶篇》《C语言初阶篇》
鸽芷咕
2023/12/25
2230
【C语言进阶】指针数组 —— 数组指针
【C 语言指针篇】指针的灵动舞步与内存的神秘疆域:于 C 编程世界中领略指针艺术的奇幻华章
指针是 C 语言中强大而精妙的工具,其在内存操作与数据处理方面展现出独特的魅力,广泛应用于各类复杂的编程场景。本篇中,我们将深入且细致地探究指针的基本原理、多样化的类型、灵活多变的运算规则。 本文我们主要来介绍指针:
意疏
2024/12/26
2010
轻松拿捏C语言——【保姆级·指针讲解】期末C语言<指针>急救包,全是干货,诚意满满!
有一栋楼,里有200个房间,假如我们要去某个房间找某个人,然后他说他在C304,我们就能通过门牌号C304快速找到他所在房间。
用户11162265
2024/06/14
1310
轻松拿捏C语言——【保姆级·指针讲解】期末C语言<指针>急救包,全是干货,诚意满满!
指针小总结
代码输出的结果是:40,如果arr是数组首元素的地址,那输出应该的应该是4/8才对。 其实数组名就是数组首元素(第⼀个元素)的地址是对的,但是有两个例外: • sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节 • &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的) 除此之外,任何地⽅使用数组名,数组名都表示首元素的地址
ljw695
2024/10/18
730
指针小总结
【C语言】指针进阶:字符指针&&数组指针&&函数指针
✨作者:@平凡的人1 ✨专栏:《C语言从0到1》 ✨一句话:凡是过往,皆为序章 ✨说明: 过去无可挽回, 未来可以改变 ---- 🌹感谢您的点赞与关注,同时欢迎各位有空来访我的🍁平凡舍 ---- 文章目录 @[toc] 🚀前言 🚀字符指针 🚀指针数组 🚀数组指针 🍁&数组名 与 数组名 🍁数组指针的使用 🚀数组传参、指针参数 🍁一维数组传参 🍁二维数组传参 🍁一级指针传参 🍁二级指针传参 🚀函数指针 🚀结语 🚀前言 回想之前,我们学了指针的一些基础👉 指针与结构体 我们知道了指针的概念
平凡的人1
2022/11/15
3.2K0
【C语言】指针进阶:字符指针&&数组指针&&函数指针
初识指针
初级指针指针是什么指针和指针类型野指针指针运算指针和数组二级指针指针数组1.指针是什么在计算机科学中,指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过指针地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元指针理解的2个要点: 指针是内存中一个最小单元的编号,也就是地址 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量举例理解假设这是内存空间,将其分为五个格子图片一个小的单元
暮云
2022/11/28
2340
推荐阅读
相关推荐
10分钟,掌握C语言指针
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验