一种更好的前端组件结构:组件树

本文翻译自 A Better Frontend Component Structure: Component Trees,作者:William Bernting, 略有删改。

一种清晰简洁的方式来查看前端项目中的前端组件(来源:Midjourney)

自很久以前遵循互联网上的建议以来,我一直采用了某种“能工作就行”的组件结构。

场景

让我们首先想象一个简化的前端应用程序目录结构,如下所示:

public/
  some-image.jpg
pages/
  index.tsx

components/
  Heading.tsx
  Logo.tsx
  Layout.tsx
  BoxContainer.tsx
  Footer.tsx

问题所在

上面的简单应用程序结构很难解释这些组件之间是如何相互作用的。

例如,您可能会猜测Layout.tsx导入Footer.tsxHeader.tsx,而这又可能导入BoxContainer.tsx。但这仅仅从文件结构上是不清楚的。

更糟糕的是,随着应用程序的增长,组件列表将变得越来越难以推断它们是如何依赖的。

简单方法:扁平组件结构

通常首先想到的是将组件组织到语义正确的目录中。

下面是这种方法的典型结果:

public/
  some-image.jpg
pages/
  index.tsx

components/
  layout/
    Layout.tsx
    Heading.tsx
    Footer.tsx
  common/
    Heading.tsx
    BoxContainer.tsx

问题#1:很难扩展好名字

作为一个开发人员,您会尝试为每个目录创建好的名称和分类,如containersheadings等。

问题是您需要为目录考虑更多的分类,而不仅仅是组件名称。

你经常会忍不住说,“我就把这个移到公共目录吧。”拥有common目录是你所追求的目标相悖的反模式,但是在这种结构下,很容易被吸引进入其中。

而且当应用程序变得足够大时,您可能不得不开始考虑创建另一级目录来保持内容的组织性。

这需要创建更多的名称,增加了存储库用户的认知负荷。最终这种方法无法很好地扩展。

问题#2:目录名称的认知负荷增加

在此之前,那些浏览代码库的人首先会通过组件的名称以及它们之间的关系来初步了解每个组件的功能。

现在他们还需要理解你创建的目录名称,如果这些名称在语义上不符合整体,这可能会使他们更加困惑。

更好的方法:组件树模式

使用这种方法,您的重点是拥有命名良好的组件,这些组件隐式地解释了它们的组成,而不用特意对具有不同名称的组件组进行分类。

组件导入规则

  • 可以向上导入,除了它自己的父级
  • 可以导入同级
  • 无法导入同级组件
  • 无法导入其父级
public/
  some-image.jpg
pages/
  index.tsx

components/
  Layout/
    components/
      Heading/
        components/
          Logo.tsx
          Menu.tsx
        Heading.tsx
      CopyrightIcon.tsx
      Footer.tsx
    Layout.tsx
  BoxContainer.tsx

让我们展示Footer.tsx的内容,并使用上面列出的规则作为示例:

// components/Layout/components/Footer.tsx

// Can import upwards, except its own parent
import { BoxContainer } from '../../BoxContainer.tsx';
// Can import siblings
import { CopyrightIcon } from './CopyrightIcon.tsx';

// WRONG: Cannot import sibling's components
// import { Menu } from './Heading/components/Menu.tsx';
// WRONG: Cannot import its parent
// import { Layout } from '../Layout.tsx';

export const Footer = () => (
  <BoxContainer>
    <CopyrightIcon />
    <p>All rights reserved, etc.</p>
  </BoxContainer>
)

优点#1:明显的子组件关系

组件树模式消除了猜测;组件之间的关系立即变得清晰明了。例如,Menu.tsx 作为 Heading.tsx 的内部依赖被整齐地嵌套在其中。

同样清晰的是Menu.tsx没有被其他任何组件使用,这有助于您在日常开发任务中清理代码时尽早忽略它。

优点2:可重用性的定义更加细致入微

在简单的方法中,组件被分为“常见”和“非常见”两种。考虑到可重用性,组件树有助于避免这种无效的二元思维。

components/
  Layout/
    components/
      Heading/
        components/
        - Logo.tsx
          Menu.tsx
        Heading.tsx
    + Logo.tsx
      CopyrightIcon.tsx
      Footer.tsx
    Layout.tsx
  BoxContainer.tsx

在上面的例子中,如果Logo.tsx对于更多的组件变得必要,而不仅仅是Menu.tsx,我们可以简单地将其上移一级。对于BoxContainer.tsx来说,它可能没有足够的可重用性(或“通用性”),但在Layout.tsx组件的上下文中它是足够可重用的。

优点#3:尽量减少命名

由于您有组件树,因此不需要将目录名分类在组件名之上。组件名称是分类,当您看到组件由哪些内部组件组成时,为组件确定好的名称也会更容易。

额外的好处:从组件中提取代码到单独的文件中,而无需考虑名称

现在我们考虑一种情况,您希望从Footer.tsx中提取一些实用程序函数,因为文件变得有点大,并且您认为可以从中分解一些逻辑,而不是分解更多的UI。

虽然你可以创建一个utils/目录,但这会迫使你选择一个文件名来放置你的实用函数。

相反,选择使用文件后缀,如Footer.utils.tsxFooter.test.tsx

components/
  Layout/
    components/
      Heading/
        components/
          Logo.tsx
          Menu.tsx
        Heading.tsx
      CopyrightIcon.tsx
    + Footer.utils.tsx
      Footer.tsx
    Layout.tsx
  BoxContainer.tsx

这样你就不必去想一个很合适的名字,如emailFormatters.ts或非常模糊的东西,如helpers.ts。避免命名带来的认知负担,这些实用程序属于Footer.tsx,可以由Footer.tsx及其内部组件使用(再次向上导入)。

组件树的反驳观点

“太多的组件目录了”

第一次看到这个结构,这是大多数人的下意识反应。

是的,有很多“组件”目录。但当我与团队一起确定项目结构时,我总是强调清晰度的重要性。

我在一个代码库中衡量成功的方法之一是高级和初级开发人员对于清晰度的看法,而在这方面,我发现组件树总是对实现这个目标起到重要作用。

“呃:import … from ./MyComponent/MyComponent.tsx?

虽然import … from ./MyComponent/MyComponent.tsx可能看起来不漂亮,但它直接指示组件来自哪里带来的清晰度更重要。

关于导入字符串,以下是为开发人员增加认知负荷的示例。

  • 使用像import ... from 'common/components'这样的导入别名对开发人员来说是一种精神负担
  • 到处都有index.ts文件,只需要写import ... from './MyComponent'。但对于按文件搜索的开发人员来说,找到正确的文件可能需要更多的时间。

最终比较:复杂场景

多亏了像ChatGPT这样的工具,为更复杂的场景测试这样的模式非常容易。

在解释了结构之后,我让ChatGPT在左列生成“平面”目录结构,在右边生成我称为“组件树”的结构。

Flat Structure                      |  Component Trees
------------------------------------+---------------------------------------------------
pages/                              |  pages/
  index.tsx                         |    index.tsx
  shop.tsx                          |    shop.tsx
  product/                          |    product/
    [slug].tsx                      |      [slug].tsx
  cart.tsx                          |    cart.tsx
  checkout.tsx                      |    checkout.tsx
  about.tsx                         |    about.tsx
  contact.tsx                       |    contact.tsx
  login.tsx                         |    login.tsx
  register.tsx                      |    register.tsx
  user/                             |    user/
    dashboard.tsx                   |      dashboard.tsx
    orders.tsx                      |      orders.tsx
    settings.tsx                    |      settings.tsx
                                    |  
components/                         |  components/
  layout/                           |    Layout/
    Layout.tsx                      |      components/
    Header.tsx                      |        Header/
    Footer.tsx                      |          components/
    Sidebar.tsx                     |            Logo.tsx
    Breadcrumb.tsx                  |            NavigationMenu.tsx
  common/                           |            SearchBar.tsx
    Button.tsx                      |            UserIcon.tsx
    Input.tsx                       |            CartIcon.tsx
    Modal.tsx                       |          Header.tsx
    Spinner.tsx                     |        Footer/
    Alert.tsx                       |          components/
  product/                          |            SocialMediaIcons.tsx
    ProductCard.tsx                 |            CopyrightInfo.tsx
    ProductDetails.tsx              |          Footer.tsx
    ProductImage.tsx                |      Layout.tsx
    ProductTitle.tsx                |    BoxContainer.tsx
    ProductPrice.tsx                |    Button.tsx
    AddToCartButton.tsx             |    Input.tsx
  filters/                          |    Modal.tsx
    SearchFilter.tsx                |    Spinner.tsx
    SortFilter.tsx                  |    Alert.tsx
  cart/                             |    ProductCard/
    Cart.tsx                        |      components/
    CartItem.tsx                    |        ProductImage.tsx
    CartSummary.tsx                 |        ProductTitle.tsx
  checkout/                         |        ProductPrice.tsx
    CheckoutForm.tsx                |        AddToCartButton.tsx
    PaymentOptions.tsx              |      ProductCard.tsx
    OrderSummary.tsx                |    ProductDetails/
  user/                             |      components/
    UserProfile.tsx                 |        ProductSpecifications.tsx
    UserOrders.tsx                  |        ProductReviews.tsx
    LoginBox.tsx                    |        ProductReviewForm.tsx
    RegisterBox.tsx                 |      ProductDetails.tsx
  about/                            |    SearchFilter.tsx
    AboutContent.tsx                |    SortFilter.tsx
  contact/                          |    Cart/
    ContactForm.tsx                 |      components/
  review/                           |        CartItemList.tsx
    ProductReview.tsx               |        CartItem.tsx
    ProductReviewForm.tsx           |        CartSummary.tsx
  address/                          |      Cart.tsx
    ShippingAddress.tsx             |    CheckoutForm/
    BillingAddress.tsx              |      components/
  productInfo/                      |        PaymentDetails.tsx
    ProductSpecifications.tsx       |        BillingAddress.tsx
  cartInfo/                         |        ShippingAddress.tsx
    CartItemList.tsx                |      CheckoutForm.tsx
  userDetail/                       |    PaymentOptions.tsx
    UserSettings.tsx                |    OrderSummary.tsx
  icons/                            |    UserProfile/
    Logo.tsx                        |      components/
    SocialMediaIcons.tsx            |        UserOrders.tsx
    CartIcon.tsx                    |        UserSettings.tsx
    UserIcon.tsx                    |      UserProfile.tsx
                                    |    LoginBox.tsx
                                    |    RegisterBox.tsx
                                    |    AboutContent.tsx
                                    |    ContactForm.tsx

这是一个没有任何测试文件、实用程序文件或类似文件的示例。

对于组件树结构,您可以在组件目录中添加后缀为的实用程序或测试文件。

至于平面结构,你可能需要创建一个单独的 utils 目录来理解已经相当复杂的认知负荷。

最后

有机会的话可以尝试这个组件结构。你会发现它是如此的直观和高效,以至于不会再回到其他更复杂的结构,它们没有简化组件管理的能力。


看完本文如果觉得有用,记得点个赞支持,收藏起来说不定哪天就用上啦~

专注前端开发,分享前端相关技术干货,公众号:南城大前端(ID: nanchengfe)

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

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

相关文章

Linux高级IO

文章目录 一.IO的基本概念二.钓鱼五人组三.五种IO模型四.高级IO重要概念1.同步通信 VS 异步通信2.阻塞 VS 非阻塞 五.其他高级IO六.阻塞IO七.非阻塞IO 一.IO的基本概念 什么是IO&#xff1f; I/O&#xff08;input/output&#xff09;也就是输入和输出&#xff0c;在著名的冯诺…

LDF文件之LDF Explorer工具

LDF Explorer工具 LDF文件比较像DBC文件&#xff0c;也是用来解析报文的&#xff0c;DBC文件是用在CAN通讯上的&#xff0c;LDF文件是用在LIN通讯上的。 我们可以用vector自带的工具LDF Explorer打开LDF文件&#xff0c;这个工具是最标准的&#xff0c;有些同学自己做了一个L…

C++基础 -18-继承中类继承的区别

无论使用公有&#xff0c;保护&#xff0c;私有继承 都无法访问基类私有成员 在多级继承中 使用公有继承 派生的派生可以访问基类的公有&#xff0c;保护成员 在多级继承中 使用保护继承 派生的派生可以访问基类的公有&#xff0c;保护成员 在多级继承中 使用私有继承 派生的派…

浅学指针(3)

系列文章目录 文章目录 系列文章目录前言系列文章目录前言1. 字符指针变量2. 数组指针变量那数组指针变量应该是&#xff1a;存放的应该是数组的地址&#xff0c;能够指向数组的指针变量。2.2 数组指针变量怎么初始化总结&#xff1a;函数名就是地址&#xff0c;&函数名和直…

【送书活动二期】Java和MySQL数据库中关于小数的保存问题

之前总结过一篇文章mysql数据库&#xff1a;decimal类型与decimal长度用法详解&#xff0c;主要是个人学习期间遇到的mysql中关于decimal字段的详解&#xff0c;最近在群里遇到一个小伙伴提出的问题&#xff0c;也有部分涉及&#xff0c;今天就再大致总结一下Java和MySQL数据库…

热门话题解析:pytest测试用例顺序问题解决方案!

前言 上一篇文章我们讲了在pytest中测试用例的命名规则&#xff0c;那么在pytest中又是以怎样的顺序执行测试用例的呢&#xff1f; 在unittest框架中&#xff0c;默认按照ACSII码的顺序加载测试用例并执行&#xff0c;顺序为&#xff1a;09、AZ、a~z&#xff0c;测试目录、测…

【软件测试学习】—软件测试的概念与软件测试模型(一)

【软件测试学习】—软件测试的概念与软件测试模型&#xff08;一&#xff09; 我 | 在这里 &#x1f469;‍&#x1f9b0;&#x1f469;‍&#x1f9b0; 读书 | 长沙 ⭐计算机科学与技术 ⭐ 本科 【2024届】 &#x1f383;&#x1f383; 爱好 | 旅游、跑步、网易云、美食、摄影…

力扣 41 42.接雨水问题详细讲解,保证看完必会接雨水问题!!!时间复杂度最优解 o(n)

首先来个开胃小菜&#xff0c;41.缺少最小整数&#xff08;难度&#xff1a;困难&#xff09;真实感觉像是个简单级别 41. 缺失的第一个正数 给你一个未排序的整数数组 nums &#xff0c;请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额…

制作心理咨询小程序的详细指南

随着科技的的发展&#xff0c;小程序已经成为了人们日常生活中不可或缺的一部分。特别是在心理咨询这个领域&#xff0c;小程序可以提供一个更为便捷、高效的服务平台。本文将通过乔拓云平台为例&#xff0c;详细介绍如何制作一个心理咨询小程序。 首先&#xff0c;我们需要注册…

销门!销售秘籍都在这了

很多刚进销售行业&#xff0c;或者初入职场的小白&#xff0c;肯定会有这一段迷茫/茫然期&#xff1a;为什么同事每天都有客户打电话/实地拜访呢&#xff1f; 而自己却无所事事&#xff0c;也没有客户找我&#xff0c;也不知道去哪里拜访客户。 这个阶段对于销售小白来说是很…

YOLOv5原创改进:全维动态卷积再改进,GCODConv

目录 一、原理 网络结构 二、代码 三、应用到YOLOv5中 一、原理

Mysql解决随机选取问题

常规的随机选取效率差的原因&#xff1a; 两种解决方法&#xff1a; 总结&#xff1a;

【Redis基础】Redis基本的全局命令

✅作者简介&#xff1a;大家好&#xff0c;我是小杨 &#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; Redis基本的全局命令 1&#xff0c;KEYS命令 语法&#xff1a;KEYS pattern KEYS命令用来查询服…

深度学习知识点

深度学习过程 data [] for i,d in enumerate(data):image,label d image,label image.cuda(),label.cuda()img net(image)optimizer.zero_grad()#需要将梯度信息清零&#xff0c;因为梯度计算是按照batch分批次计算的&#xff0c;如果这一批batch没清零&#xff0c;会影响…

深入理解堆排序:建堆、排序与优化

引言 在计算机科学中&#xff0c;堆排序是一种高效的排序算法&#xff0c;利用堆的数据结构特性进行排序。本文将深入探讨堆排序的原理、实现过程&#xff0c;并介绍一种优化方法&#xff0c;以帮助读者更好地理解和运用这一经典算法 目录 堆排序简介 1.1 什么是堆排序&#x…

Vue生命周期

生命周期 Vue.js 组件生命周期&#xff1a; 生命周期函数&#xff08;钩子&#xff09;就是给我们提供了一些特定的时刻&#xff0c;让我们可以在这个周期段内加入自己的代码&#xff0c;做一些需要的事情; 生命周期钩子中的this指向是VM 或 组件实例对象 在JS 中&#xff0c;…

JRT实现缓存协议

上一篇介绍的借助ORM的增、删、改和DolerGet方法&#xff0c;ORM可以很精准的知道热点数据做内存缓存。那么就有一个问题存在&#xff0c;即部署了多个站点时候&#xff0c;如果用户在一个Web里修改数据了&#xff0c;那么其他Web的ORM是不知道这个变化的&#xff0c;其他Web还…

局部性原理和伪共享

CPU Cache CPU Cache可以理解为CPU内部的高速缓存。CPU从内存读取数据时&#xff0c;将要读取的数据及其相邻地址的数据&#xff0c;即至少一个Cache Line&#xff0c;写入Cache&#xff0c;以便后续访问时提高读取速度。 CPU存在多级Cache&#xff0c;级别最高的离CPU最近&a…

实现电商平台与营销系统无缝集成:雅座的无代码开发与API连接

无代码开发&#xff1a;营销的新引擎 在数字化转型的浪潮中&#xff0c;无代码开发已成为企业提升效率、减少成本的新引擎。这种开发方式允许非技术人员通过图形界面构建应用程序&#xff0c;无需编写代码即可实现复杂功能。这对于营销、广告推广以及用户运营等业务尤为重要&a…

贪心 53. 最大子序和 122.买卖股票的最佳时机 II

53. 最大子序和 题目&#xff1a; 给定一个数组&#xff0c;有正有负&#xff0c;找出一个连续子序列的总和最大&#xff08;子数组最少一个&#xff09; 暴力思路&#xff1a; 双层for循环&#xff0c;记录每一次可能的子序列的总和&#xff0c;初始为整数最小值&#xff…