[React 进阶系列] 组合组件 复合组件

[React 进阶系列] 组合组件 & 复合组件

今天写个人项目练手的时候搜到了一个比价有趣的实现,于是用了一下,发现这个 concept 不是特别的熟,于是上网找了下,返现了一个叫 复合组件(compound components) 的概念。搜索了一下后,发现 csdn 上关于这方面的比较少,很多搜出来的结果虽然写的是 复合组件,但是实际上的逻辑更像是 组合组件(composite components)

甚至是 deepseek 给出来的结果都有些混淆:

React 复合组件设计模式
复合组件是一种常见的 React 设计模式,它通过组合多个子组件来构建复杂的 UI 结构。这种模式的核心理念在于利用 props.children 和上下文传递数据的方式实现父子组件之间的通信。

基本概念

复合组件模式允许父组件控制其子组件的行为和外观,而不需要直接操作这些子组件的状态或属性。这种方式增强了可重用性和灵活性 1。

使用场景

当需要创建一组紧密关联的组件时,可以采用此模式。例如,在表单库中,可能有一个 组件作为容器,其中包含若干输入字段(如 , 等)。每个字段都依赖于 提供的数据环境。

实现方式

以下是实现复合组件的一些关键点:

Context API: 利用 Context 来共享状态或者方法给所有的后代节点。

Render Props: 子组件可以通过 render prop 函数接收来自父级的信息并据此渲染自己的一部分视图逻辑。

下面是一个简单的例子展示如何使用 Composite Pattern 构建一个 Accordion(手风琴) 组件:

看这里的解释,核心概念还是用 composite pattern 而非 compound pattern

所以打算就这自己的理解写一下笔记,如果有对此比较了解到大佬可以更加深入的探讨学习一下就好了

大体总结一下就是:

  • Compound Components 通过共享状态的方式构建组件组

    强调父组件对子组件的控制;

  • Composite Components 注重松耦合的组合与复用

复合组件 compound components

这个还是在搜索 colocation 这个关键词的时候慢慢从脑子里面跳出来,随后自己写了点东西出来,发现写出来的调用方法和之前记得一些 UI 库的使用方法很像,于是上网搜了下,发现了这个 design pattern

先说总结,compound components 的使用场景为:

  • 子组件必须依附于父组件的 context 和 state
  • 父子组件的逻辑非常清晰,其结构不应该被随意修改
  • 子组件不可/不应该独立存在

目前用这个 pattern 比较多的库有

  • react-bootstrap

    应该说 bootstrap 本身的设计思路就是基于 compound components 实现的

    我找了下文档,目前来说一些表单类的还是比较依赖于 compound components,不过其他的一些实现,比如说 Grid 和 Stack 也是转向了 composite components 的设计

  • React Router

    这不是个 UI 库,不过设计思路上是符合 compound components

    Route 是不能够在 Routes 外实现的,并且 Route 的状态由 Routes 内部管理

  • formik

    这个的表单管理还是依赖于父组件状态的

  • 一些用的不是特别多的 UI 库,如 Radix UI, Semantic UI 之类的

大体的使用方法如下:

import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";function BasicExample() {return (<Form><Form.Group className="mb-3" controlId="FormBasicEmail"><Form.Label>Email address</Form.Label><Form.Control type="email" placeholder="Enter email" /><Form.Text className="text-muted">We'll never share your email with anyone else.</Form.Text></Form.Group><Form.Group className="mb-3" controlId="FormBasicPassword"><Form.Label>Password</Form.Label><Form.Control type="password" placeholder="Password" /></Form.Group><Form.Group className="mb-3" controlId="FormBasicCheckbox"><Form.Check type="checkbox" label="Check me out" /></Form.Group><Button variant="primary" type="submit">Submit</Button></Form>);
}export default BasicExample;

这是从 react-bootstrap 上拉下来的一个案例,可以看到,其核心概念是:

  • 子组件 必须 包括在父组件内

    即有一个很明显的阶级结构,曾经 grid 也是这么实现的,Grid.Col 必须是要在 Grid 的结构目录下,如果不这么做,那么样式就会变得不太可控

    这也是为什么一些表单类的其实还是比较适合用这种结构,但是一些 UI 类的就不太适合了,毕竟 Grid.ColFlex.Col 的重复功能比较多

    对于开发者来说,嵌套 Grid 和 Flex 也会让代码的结构过于复杂,使得阅读性和管理都变得有些困难——特别是一些表单的业务逻辑特别复杂的情况下

  • 子组件的状态会依赖于 context 或者父组件的状态

    这个其实 formik、react router 也表单类的相关库可以看得出来

  • 组件之间的耦合度很高

我现在工作的公司内部 UI 库,至少是支持 React 的这个,还是在使用 compound components,这也会导致一些情况下——需要嵌套 From、Grid、Flex 的情况,代码就挺乱的。而且我们其实对于 css 没什么办法去重写,一旦遇到一些问题,就只能继续增加嵌套,然后重写 css 去想办法实现用户的需求,这也是为啥会有多重嵌套的烦恼

因此我个人是觉得,除非出现业务逻辑真的有强关联的情况——如 form、router 这种,大多数情况下,普通的 UI 逻辑其实没有必要使用 compound components

我这次主要是想尝试一下实现功能,大体实现的业务逻辑如下:

import React from "react";const StatGrid = ({children,columns = "grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-4",gap = "gap-7",
}) => {return <div className={`w-full grid ${columns} ${gap}`}>{children}</div>;
};const StatCard = ({title,subtitle,icon: Icon,cardBg = "#f5f5f5",iconBg = "#333",textColor = "#5c5a5a",
}) => {return (<divclassName="flex justify-between items-center p-5 rounded-md gap-3"style={{ backgroundColor: cardBg }}><divclassName="flex flex-col justify-start items-start"style={{ color: textColor }}><h2 className="text-3xl font-bold">{title}</h2><span className="text-md font-medium">{subtitle}</span></div><divclassName="w-[40px] h-[47px] rounded-full flex justify-center items-center text-xl"style={{ backgroundColor: iconBg }}>{Icon && <Icon className="text-[#fae8e8] shadow-lg" />}</div></div>);
};StatGrid.Card = StatCard;
export default StatGrid;

在这里插入图片描述

其实从业务逻辑上来说,StatCard 与其父组件并没有构成绝对意义上的强关联,至少关联性没有强到需要用到 compound components 的程度,这个也只是想尝试性实验

除了上面写的,直接使用静态属性挂载的方法实现自组件,另一种写法更加的严苛,可以过滤掉所有不属于对应自组件的元素:

const StatGrid = ({ children }) => {const cards = _.chain(React.Children.toArray(children)).filter((child) => _.get(child, "type.displayName") === "StatGrid.Card").value();return <div>{cards}</div>;
};const StatCard = ({ children }) => <div>{children}</div>;
StatCard.displayName = "StatGrid.Card";
StatGrid.Card = StatCard;

组合组件 composite components

这是一个在 React 中非常常见的使用场景,React 官方文档也是更加推荐使用 composition 而不是 inheritance

事实上我个人感觉,大部分的 UI 库已经慢慢转向 composite components 的实现,毕竟这样的实现更佳的扁平化,而且这样的配置对 config array 的支持比较友好,总体来说 DX 体验感更好

一些比较常见的案例包括:

  • 将 header,footer,body 组合,形成一个新的 wrapper 组件,并将其返回以减少代码的重复利用

  • 通过嵌套一些第三方库提供的组件,形成一个 customized 的组件去使用,减少代码的重复性

    比如说可以使用 react-icons + react-router-dom 提供的 Link 拼接成一个 clickable icon button

之前也提到了,记忆中 antd 和 MUI 还是使用 compound components 的,不过今天看了下最新的文档,应该说实现已经完全不一样了,其大体原因还是与复用性有关

如 antd/MUI 的 form 结构其实已经不需要依附于它们所提供的 Form 组件,而是让开发者自己去进行管理,这个时候更加扁平化的设计可以比较简单的添加、修改样式;真正的核心状态管理则可以让开发自己进行实现

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

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

相关文章

HarmonyOS NEXT 鸿蒙中关系型数据库@ohos.data.relationalStore API 9+

核心API ohos.data.relationalStore API 9 数据库 数据库是存储和管理数据的系统 数据库&#xff08;Database&#xff09;是一个以特定方式组织、存储和管理数据的集合&#xff0c;通常用于支持各种应用程序和系统的运行。它不仅是存放数据的仓库&#xff0c;还通过一定的…

用HTML和CSS生成炫光动画卡片

这个效果结合了渐变、旋转和悬浮效果的炫酷动画示例&#xff0c;使用HTML和CSS实现。 一、效果 二、实现 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport&quo…

蓝桥杯第10届 后缀表达式

题目描述 给定 N 个加号、M 个减号以及 NM1 个整数 A1,A2,⋅⋅⋅,ANM1​&#xff0c;小明想知道在所有由这N 个加号、M 个减号以及 NM1 个整数凑出的合法的 后缀表达式中&#xff0c;结果最大的是哪一个&#xff1f; 请你输出这个最大的结果。 例如使用 1 2 3 -&#xff0c…

常见框架漏洞攻略-ThinkPHP篇

漏洞名称&#xff1a;Thinkphp5x远程命令执行及getshell 第一步&#xff1a;开启靶场 第二步&#xff1a;准备工具 第三步&#xff1a;启动工具&#xff0c;进行漏洞检测 #存在漏洞 1.目标存在tp5_invoke_func_code_exec_1漏洞2.目标存在tp5_dbinfo_leak漏洞payload:http://47…

sql长时间卡在gc current request事件

问题描述 凌晨跑批出现超时。SQL f0ng33agbpzhs业务需要执行160w次左右。现场人员杀掉该sql&#xff0c;重新发起业务&#xff0c;业务批次成功跑完。 问题分析 总体sql分析 分析对比sql的awrsqrpt&#xff0c;对比昨天3月8日的。 总体执行次数没有变化。Cpu时间、物理读等均…

MOSN(Modular Open Smart Network)-04-TLS 安全链路

前言 大家好&#xff0c;我是老马。 sofastack 其实出来很久了&#xff0c;第一次应该是在 2022 年左右开始关注&#xff0c;但是一直没有深入研究。 最近想学习一下 SOFA 对于生态的设计和思考。 sofaboot 系列 SOFAStack-00-sofa 技术栈概览 MOSN&#xff08;Modular O…

使用 Python 开发 MCP Server 及 Inspector 工具详解

使用 Python 开发 MCP Server 及 Inspector 工具详解 前言 模型上下文协议 (Model Context Protocol, MCP) 是一种新兴的协议&#xff0c;旨在让大型语言模型 (LLM) 更容易地与外部工具和服务集成。本文将介绍如何使用 Python 开发一个 MCP Server&#xff0c;并详细讲解如何使…

深入剖析 IS - IS 路由协议的原理、配置及与 OSPF 的对比

目录 ISIS概述 NSAP&#xff08;类似于IP地址&#xff09; NET NET配置举例 IS-IS 和OSPF区域划分的区别 区域和区域的分界点 IS-IS路由器的分类 Level-1路由器 Level-2路由器 Level-1-2路由器 ISIS支持的网络类型 ISIS开销值 IS-IS报文格式 IS-IS报文类型概述…

【deepseek 学c++】weakptr引用场景

std::weak_ptr 是 C 中与 std::shared_ptr 配合使用的智能指针&#xff0c;它本身不拥有资源的所有权&#xff0c;仅观察资源的状态&#xff0c;主要用于解决 shared_ptr 的循环引用问题和临时访问共享资源的需求。以下是 weak_ptr 的典型应用场景和核心价值&#xff1a;![ 为…

23种设计模式-适配器(Adapter)设计模式

适配器设计模式 &#x1f6a9;什么是适配器设计模式&#xff1f;&#x1f6a9;适配器设计模式的特点&#x1f6a9;适配器设计模式的结构&#x1f6a9;适配器设计模式的优缺点&#x1f6a9;适配器设计模式的Java实现&#x1f6a9;代码总结&#x1f6a9;总结 &#x1f6a9;什么是…

R语言对偏态换数据进行转换(对数、平方根、立方根)

我们进行研究的时候经常会遇见偏态数据&#xff0c;数据转换是统计分析和数据预处理中的一项基本技术。使用 R 时&#xff0c;了解如何正确转换数据有助于满足统计假设、标准化分布并提高分析的准确性。在 R 中实现和可视化最常见的数据转换&#xff1a;对数、平方根和立方根转…

REC一些操作解法

一.Linux命令长度突破 1.源码如下 <?php $param $_REQUEST[param];if ( strlen($param) < 8 ) {echo shell_exec($param); } 2.源码分析 echo执行函数&#xff0c;$_REQUEST可以接post、get、cookie传参 3.破题思路 源码中对参数长度做了限制&#xff0c;小于8位&a…

16个气象数据可视化网站整理分享

好的&#xff01;以下是关于“16个气象数据可视化网站整理分享”的软文&#xff1a; 16个气象数据可视化网站整理分享 气象数据可视化已成为现代气象研究、决策支持以及公众天气服务的重要组成部分。从天气预报到气候变化监测&#xff0c;全球许多气象数据可视化平台为专业人士…

Stereolabs ZED Box Mini:机器人与自动化领域的人工智能视觉新选择

在人工智能视觉技术快速发展的今天&#xff0c;其应用场景正在持续拓宽&#xff0c;从智能安防到工业自动化&#xff0c;从机器人技术到智能交通&#xff0c;各领域都在积极探索如何利用这一先进技术。而 Stereolabs 推出的ZED Box Mini&#xff0c;正是一款专为满足这些多样化…

LeetCode热题100|128.最长连续序列,283.移动零

128.最长连续序列 题目链接&#xff1a;128. 最长连续序列 - 力扣&#xff08;LeetCode&#xff09; 这里要求的一个乱序的数组里连续数字的个数&#xff0c;比如【100 &#xff0c;4&#xff0c;200&#xff0c;1&#xff0c;3&#xff0c;2】 里面连续的数字就是【1&#…

Unity-RectTransform设置UI width

不知道有没人需要这样的代码&#xff0c;就是.sizeDelta //不确定是不是英文翻译的原因&#xff0c;基本很难理解&#xff0c;sizeDeltaSize&#xff0c;//未必完全正确&#xff0c;但这么写好像总没错过 //image 在一个UnityEngine.UI.Image 的数组内foreach (var image in l…

GZCTF平台搭建及题目上传

前言 我用手里的Ubuntu虚拟机搭建的&#xff0c;大家根据自己的实际情况来吧 安装及部署 首先&#xff0c;你的虚拟机需要有Docker和Docker-Compose&#xff0c;前者可以看我之前的文章&#xff0c;另外一个可以输入下面的命令安装&#xff0c;注意先获取管理员权限&#xff…

记录Jmeter 利用BeanShell 脚本解析JSON字符串

下载org.json包(文档说明) #下载地址 https://www.json.org/ # github 地址 https://github.com/stleary/JSON-java # api 文档说明 https://resources.arcgis.com/en/help/arcobjects-java/api/arcobjects/com/esri/arcgis/server/json/JSONObject.htmlBeanShell脚本 import…

在Centos 7环境下安装MySQL

前言&#xff1a;在安装与卸载MySQL时&#xff0c;用户需切换为root&#xff0c;这样安装之后&#xff0c;普通用户也能够使用。 Tips:我们在刚开始学习时&#xff0c;尽量全部使用root进行&#xff0c;适应mysql语句&#xff0c;后面学了用户管理&#xff0c;就可以考虑新建普…

使用HTML5和CSS3实现3D旋转相册效果

使用HTML5和CSS3实现3D旋转相册效果 这里写目录标题 使用HTML5和CSS3实现3D旋转相册效果项目介绍技术栈核心功能实现思路1. HTML结构2. CSS样式解析2.1 基础样式设置2.2 3D效果核心样式2.3 卡片样式 3. JavaScript交互实现3.1 旋转控制3.2 自动播放功能 技术要点总结项目亮点总…