R

React 项目结构规模化:分解、层级和层次结构

2025-06-08

React 项目结构规模化:分解、层级和层次结构

图片描述

最初发表于https://www.developerway.com。该网站还有更多类似的文章 😉

...

如何以“正确的方式”构建 React 应用程序似乎是一个热门话题 最近自 React 诞生以来,它就一直存在。React 官方对此的评价是“没有意见”。这很好,它给了我们完全的自由去做任何我们想做的事情。但同时也很糟糕。这导致人们对 React 应用的正确结构产生了如此多截然不同且非常强烈的意见,以至于即使是最有经验的开发人员有时也会感到迷茫、不知所措,甚至想躲在黑暗的角落里哭泣。

当然,我对这个话题也持有强烈的看法😈。而且这次甚至不会再说“视情况而定”了😅(几乎)。今天我想分享的是这个系统,我发现它在以下方面运行良好:

  • 同一个存储库中有数十个松散连接的团队在同一个产品上工作
  • 在只有几名工程师的小型初创公司的快节奏环境中
  • 甚至用于个人项目(是的,我一直用它来做我的个人事情)

请记住,与海盗法典一样,所有这些都只是所谓的“指导方针”,而不是实际规则。

我们需要从项目结构约定中得到什么

我不想详细解释为什么我们需要这样的约定:如果你读了这篇文章,你可能已经决定你需要它了。不过,在深入探讨解决方案之前,我想先简单谈谈是什么让项目结构约定如此优秀。

可复制性

代码规范应该易于理解,并且团队中的任何成员都能轻松复现,即使是刚入职、经验不足的实习生。如果你的代码库需要博士学位、几个月的培训以及对每个 PR 进行深入的哲学辩论……那么,这或许会是一个非常漂亮的系统,但它除了纸面上的东西之外,不会存在于任何地方。

可推断性

你可以写一本书,拍几部关于“我们的代码库工作方式”的电影。你甚至可以说服团队里的每个人都去阅读和观看它(尽管你可能不会这么做)。但事实是:大多数人不会记住它的每一个字,甚至根本不会记住。为了使这个惯例真正发挥作用,它应该非常明显和直观,以便团队成员理想情况下能够通过阅读代码来对其进行逆向工程。在理想情况下,就像代码注释一样,你甚至不需要把它写在任何地方——代码和结构本身就是你的文档。

独立

多人(尤其是多团队)编码结构指南最重要的要求之一是,要确保开发人员能够独立工作。最不希望看到的是多个开发人员在同一个文件上工作,或者团队之间不断侵犯彼此的职责范围。

因此,我们的编码结构指南应该提供这样一种结构,让团队能够在同一个存储库中和平共处。

针对重构进行了优化

最后一点,但在现代前端世界中,它却是最重要的一点。如今的前端瞬息万变。模式、框架和最佳实践都在不断变化。最重要的是,我们被期望快速交付功能。不,是“快”。然后一个月后就完全重写。然后可能还要再重写一遍。

因此,我们的编码规范至关重要,它不应该强迫我们将代码“粘”在某个固定的位置,无法移动。它应该以一种让重构成为日常工作中随意执行的事情的方式组织起来。规范最糟糕的结果就是让重构变得如此困难和耗时,以至于每个人都对它感到恐惧。相反,重构应该像呼吸一样简单。

...

现在,我们已经了解了项目结构约定的总体要求,是时候深入探讨细节了。让我们先从总体情况开始,然后再深入探讨细节。

组织项目本身:分解

组织一个符合我们上述原则的大型项目,首要且最重要的部分是“分解”:与其将其视为一个单体项目,不如将其视为由或多或少独立的功能组合而成。这就像老生常谈的“单体”与“微服务”的争论,只不过是在同一个 React 应用程序中进行。采用这种方法,每个功能本质上都是一个“纳米服务”,与其他功能隔离,并通过外部“API”(通常只是 React props)与它们通信。

图片描述

即使仅仅遵循这种思维模式,与更传统的“React 项目”方法相比,你也能获得我们上面列出的几乎所有功能:如果团队/人员将功能实现为一堆相互连接在一起的“黑匣子”,他们将能够独立地并行开发这些功能。如果设置正确,任何人都应该能够一目了然,只是需要一些练习来适应思维转变。如果你需要移除某个功能,你可以直接“拔掉”它,或者用其他功能替换它。或者,如果你需要重构某个功能的内部结构,你也可以做到。只要它的公共“API”保持功能正常,外部任何人都不会注意到它。

我描述的是 React 组件,对吧?😅 嗯,概念是一样的,这使得 React 非常适合这种思维方式。为了区别于“组件”,我会将“功能”定义为“一组组件和其他元素组合在一起,从最终用户的角度看,构成一个完整的功能”。

那么,如何为单个项目组织这些功能呢?尤其考虑到与微服务相比,它所需的管道要少得多:在一个包含数百个功能的项目中,将它们全部提取到实际的微服务中几乎是不可能的。我们可以做的是使用多包的 Monorepo架构:它非常适合将独立的功能组织和隔离为包。任何从 npm 安装过任何东西的人都应该已经熟悉包的概念。Monorepo 只是一个仓库,其中有多个包的源代码和谐共存,共享工具、脚本、依赖项,有时甚至相互共享。

所以概念很简单:React 项目→将其拆分为独立的功能→将这些功能放入包中。

图片描述

如果您从未使用过本地设置的 Monorepo,而现在,在我提到“包”和“npm”之后,您对发布您的私人项目感到不安:不必担心。发布和开源都不是 Monorepo 存在以及开发者从中获益的必要条件。从代码角度来看,包只是一个文件夹,其中包含具有某些属性的文件。然后,该文件夹通过 Node 的符号链接package.json链接到安装了“传统”包的文件夹。这种链接由YarnNpm等工具本身执行:它被称为“工作区”,它们都支持此功能。它们使您可以在本地代码中访问这些包,就像从 npm 下载的任何其他包一样。node_modules

它看起来是这样的:

/packages
  /my-feature
    /some-folders-in-feature
    index.ts
    package.json // this is what defines the my-feature package
  /another-feature
    /some-folders-in-feature
    index.ts
    package.json // this is what defines the another-feature package
Enter fullscreen mode Exit fullscreen mode

在 package.json 中我将包含以下两个重要字段:

{
  "name": "@project/my-feature",
  "main": "index.ts"
}
Enter fullscreen mode Exit fullscreen mode

其中,“name”字段显然是包的名称——基本上是这个文件夹的别名,通过这个别名,仓库中的代码就可以访问它。“main”是包的主入口,也就是说,当我写下类似以下内容时,哪个文件会被导入:

import { Something } from '@project/my-feature';
Enter fullscreen mode Exit fullscreen mode

有不少知名项目的公共存储库都使用多包 monorepo 方法:例如BabelReactJest等等。

为什么使用包而不是文件夹

乍一看,这些软件包的做法就像“把你的功能拆分成文件夹,有什么大不了的”,似乎没什么突破性。然而,软件包能提供一些有趣的功能,而简单的文件夹却无法做到。

别名。使用包,您可以按名称而不是按位置来引用功能。比较一下:

import { Button } from '@project/button';
Enter fullscreen mode Exit fullscreen mode

采用这种更“传统”的方法:

import { Button } from '../../components/button';
Enter fullscreen mode Exit fullscreen mode

在第一次导入中,很明显 - 我正在使用我的项目的通用“按钮”组件,即我的设计系统版本。

第二个按钮不太清楚——这个按钮是什么?是通用的“设计系统”按钮吗?还是这个功能的一部分?或者是“上面”的功能?我能在这里用它吗?也许它是为某些非常特殊的用例编写的,而这些用例在我的新功能中无法使用?

如果你的仓库中有多个“utils”或“common”文件夹,情况会更糟。我最可怕的代码噩梦是这样的:

import { bla } from '../../../common';
import { blabla } from '../../common';
import { blablabla } from '../common';
Enter fullscreen mode Exit fullscreen mode

使用包的话,它可能看起来像这样:

import { bla } from '@project/button/common';
import { blabla } from '@project/something/common';
import { blablabla } from '@project/my-feature/common';
Enter fullscreen mode Exit fullscreen mode

一目了然地了解哪些代码来自哪里,哪些代码应该放在哪里。而且,很有可能,“my-feature” 中的“通用”代码仅供该功能内部使用,从未打算在该功能之外使用,在其他地方重复使用它是个坏主意。有了包,您可以立即看到它。

关注点分离。考虑到我们都习惯了 npm 中的包以及它们所代表的含义,当你的功能被直接写成一个“包”时,就更容易将其视为一个具有自己公共 API 的独立模块。

看看这个:

import { dateTimeConverter } from '../../../../button/something/common/date-time-converter';
Enter fullscreen mode Exit fullscreen mode

与此相比:

import { dateTimeConverter } from '@project/button';
Enter fullscreen mode Exit fullscreen mode

第一个很可能会淹没在周围的导入语句中,不被察觉,让你的代码变成一团乱麻。第二个会立即自然而然地引起一些人的注意:日期时间转换器?一个按钮?真的吗?这自然会迫使你在不同的功能/包之间划出更清晰的界限。

内置支持。您无需发明任何东西,大多数现代工具(例如 IDE、typescript、linting 或 bundlers)都支持开箱即用的软件包。

重构轻而易举。将功能拆分成包后,重构变得轻松有趣。想要重构包的内容?没问题,您可以完全重写,只要保持条目的 API 不变,代码库的其余部分就不会察觉到。想要将包移动到其他位置?只需拖放文件夹即可,如果您不重命名,代码库的其余部分不会受到影响。想要重命名包?只需在项目中搜索并替换字符串即可,无需其他操作。

显式入口点。如果您真正秉持“只向用户开放公共 API”的理念,您可以非常明确地规定包中哪些内容可供外部用户使用。例如,您可以限制所有“深层”导入,使类似的事情@project/button/some/deep/path无法发生,并强制所有人只能使用 index.ts 文件中明确定义的公共 API。请查看包入口点包导出文档,了解其工作原理的示例。

如何将代码拆分成包

在多包架构中,人们最纠结的问题是什么时候应该将代码提取到一个包中?每个小功能都应该放在一个包中吗?还是说,包只适用于像整个页面甚至整个应用程序这样的大功能?

根据我的经验,这里需要权衡一下。你肯定不想把所有小东西都提取到一个包里:这样最终只会得到一个包含数百个单文件、毫无结构的小包的扁平列表,这多少违背了引入这些包的初衷。同时,你也不希望你的包变得太大:我们试图解决的所有问题,都只会在这个包里遇到。

以下是我通常使用的一些界限:

  • “设计系统” 类型的东西,比如按钮、模式对话框、布局、工具提示等等,都应该是包
  • 一些“自然” UI 边界内的功能非常适合打包,例如模态对话框、抽屉、滑入面板等
  • “可共享”功能——可以在多个地方使用的功能
  • 你可以将其描述为具有明确边界、逻辑性且理想情况下在 UI 中可见的独立“功能”

另外,与上一篇关于如何将代码拆分成组件的文章一样,一个包只负责一个概念性的功能非常重要。如果一个包导出了一个ButtonCreateIssueDialog同时DateTimeConverter做了太多事情,就需要拆分。

如何组织包裹

虽然可以创建一个包含所有软件包的简单列表,并且对于某些类型的项目来说确实有效,但对于大型、重 UI 的产品来说,这可能不够。看到“工具提示”和“设置页面”之类的软件包放在一起,我就感到很不舒服。或者更糟的是——如果你把“后端”和“前端”软件包放在一起。这不仅混乱,而且危险:你最不想发生的事就是不小心把一些“后端”代码拖进你的前端包里。

实际的仓库结构很大程度上取决于你正在实现的产品具体是什么(甚至有多少个产品)、你只有后端还是前端,并且可能会随着时间的推移而发生重大变化和演变。幸运的是,这正是软件包的巨大优势:实际结构完全独立于代码,如有需要,你可以每周拖放并重新构建它们一次,而不会产生任何后果。

考虑到结构上的“错误”成本很低,至少在一开始就没必要过度思考。如果你的项目只涉及前端,你甚至可以从一个扁平列表开始:

/packages
  /button
  ...
  /footer
  /settings
  ...
Enter fullscreen mode Exit fullscreen mode

并随着时间的推移将其演变成如下的样子:

/packages
  /core
    /button
    /modal
    /tooltip
    ...
  /product-one
    /footer
    /settings
    ...
  /product-two
    ...
Enter fullscreen mode Exit fullscreen mode

或者,如果你有一个后端,它可能是这样的:

/packages
  /frontend
    ... // the same as above
  /backend
    ... // some backend-specific packages
  /common
    ... // some packages that are shared between frontend and backend
Enter fullscreen mode Exit fullscreen mode

在“公共”部分,你会放一些前后端共享的代码。通常是一些配置、常量、类似 lodash 的工具、共享类型。

如何构建包本身

总结一下上面的大段内容:“使用 Monorepo,将功能提取到包中”。🙂 现在进入下一部分——如何组织包本身。对我来说,这里有三件事很重要:命名约定、将包分成不同的层级以及严格的层级结构。

命名约定

每个人都喜欢命名,也喜欢争论别人的命名有多糟糕,不是吗?为了减少在无休止的 GitHub 评论中浪费的时间,也为了安抚像我这样患有代码强迫症的可怜极客,最好一次性地为所有人统一一个命名约定。

在我看来,使用哪一个其实并不重要,只要在整个项目中始终如一地遵循即可。如果你在同一个仓库里,有ReactFeatureHere.ts一只react-feature-here.ts小猫在某处哭泣😿。我通常使用这个:

/my-feature-name
  /assets     // if I have some images, then they go into their own folder
    logo.svg
  index.tsx   // main feature code
  test.tsx    // tests for the feature if needed
  stories.tsx // stories for storybooks if I use them
  styles.(tsx|scss) // I like to separate styles from component's logic
  types.ts    // if types are shared between different files within the feature
  utils.ts    // very simple utils that are used *only* in this feature
  hooks.tsx   // small hooks that I use *only* in this feature
Enter fullscreen mode Exit fullscreen mode

如果某个功能有几个较小的组件直接导入到中index.tsx,它们将如下所示:

/my-feature-name
  ... // the same as before
  header.tsx
  header.test.tsx
  header.styles.tsx
  ... // etc
Enter fullscreen mode Exit fullscreen mode

或者,更有可能的是,我会立即将它们提取到文件夹中,它们看起来像这样:

/my-feature-name
  ... // index the same as before
  /header
    index.tsx
    ... // etc, exactly the same naming here
  /footer
    index.tsx
    ... // etc, exactly the same naming here
Enter fullscreen mode Exit fullscreen mode

文件夹方法更适合复制粘贴驱动的开发😊:当通过复制粘贴附近功能的结构来创建新功能时,您只需重命名一个文件夹即可。所有文件都将以完全相同的名称命名。此外,创建包的心理模型、重构和移动代码也更容易(下一节将对此进行介绍)。

包内的层

一个典型的具有复杂功能的包会包含几个不同的“层”:至少有“UI”层和“数据”层。虽然可以把所有层混合在一起,但我还是不建议这样做:渲染按钮和从后端获取数据是截然不同的关注点。将它们分开可以让包更具结构性和可预测性。

为了使项目在架构和代码方面保持相对健康,关键是能够清楚地识别对您的应用程序很重要的那些层,映射它们之间的关系,并以与所使用的工具和框架一致的方式组织所有这些。

如果我今天从头开始实现一个 React 项目,使用 Graphql 进行数据操作,使用纯 React 状态进行状态管理(即没有 Redux 或任何其他库),我将拥有以下层:

  • “数据”层 - 负责查询、修改以及其他连接外部数据源并进行转换的功能。仅供 UI 层使用,不依赖于任何其他层。
  • “共享”层 - 各种实用程序、函数、钩子、小组件、类型和常量,供整个包中的所有其他层使用。不依赖于任何其他层。
  • “ui”层 - 实际的功能实现。依赖于“data” 和“shared” 层,其他层不依赖它。

就是这样!

图片描述

如果我使用一些外部状态管理库,我可能也会添加“状态”层。该层很可能是“数据”和“用户界面”之间的桥梁,因此会使用“共享”层和“数据”层,而“用户界面”会使用“状态”而不是“数据”。

图片描述

从实现细节来看,所有层都是包中的顶级文件夹:

/my-feature-package
  /shared
  /ui
  /data
  index.ts
  package.json
Enter fullscreen mode Exit fullscreen mode

每个“层”都使用上面描述的命名约定。因此,你的“数据”层看起来应该像这样:

/data
  index.ts
  get-some-data.ts
  get-some-data.test.ts
  update-some-data.ts
  update-some-data.test.ts
Enter fullscreen mode Exit fullscreen mode

对于更复杂的包,我可能会将这些层分开,同时保留它们的用途和特性。例如,“数据”层可以拆分为“查询”(“getters”)和“修改”(“setters”),它们可以保留在“数据”文件夹中,也可以上移:

/my-feature-package
  /shared
  /ui
  /queries
  /mutations
  index.ts
  package.json
Enter fullscreen mode Exit fullscreen mode

图片描述

或者您可以从“共享”层中提取一些子层,例如“类型”和“共享 UI 组件”(这会立即将此子层转换为“UI”类型,因为除了“UI”之外没有人可以使用 UI 组件)。

/my-feature-package
  /shared-ui
  /ui
  /queries
  /mutations
  /types
  index.ts
  package.json
Enter fullscreen mode Exit fullscreen mode

图片描述

只要您能够清楚地定义每个“子层”的用途,清楚哪个“子层”属于哪个“层”,并且可以将其形象化并向团队中的每个人解释 - 一切就都行!

层内有严格的层次结构

最后一个难题是,为了让这个架构可预测且易于维护,层级之间要有严格的层次结构。这一点在 UI 层尤为明显,因为在 React 应用中,UI 层通常是最复杂的。

例如,让我们开始搭建一个简单的页面,其中包含页眉和页脚。我们将创建一个“index.ts”文件(页面的主文件),以及“header.ts”和“footer.ts”组件。

/my-page
  index.ts
  header.ts
  footer.ts
Enter fullscreen mode Exit fullscreen mode

现在,所有应用都会有各自的组件,我希望把它们放在各自的文件中。例如,“Header” 会包含“搜索栏”和“发送反馈”组件。按照“传统”的扁平化应用组织方式,我们会把它们并排摆放,不是吗?应该是这样的:

/my-page
  index.ts
  header.ts
  footer.ts
  search-bar.ts
  send-feedback.ts
Enter fullscreen mode Exit fullscreen mode

然后,如果我想在页脚组件中添加相同的“发送反馈”按钮,我还是需要将其从“send-feedback.ts”导入到“footer.ts”中,对吗?毕竟它就在附近,而且看起来很自然。

图片描述

不幸的是,刚刚发生的事情是,我们毫无察觉地打破了层级(“UI”和“共享”)之间的界限。如果我继续在这个扁平结构中添加越来越多的组件(我很可能这么做,因为实际的应用程序往往非常复杂),我很可能会再次违反这些界限。这会把这个文件夹变成一个小小的“泥球”,完全无法预测哪个组件会依赖哪个组件。因此,当重构的时候,理清所有这些并从这个文件夹中提取出一些东西,可能会变成一项非常令人头疼的工作。

相反,我们可以用分层的方式来构建这一层。规则如下:

  • 只有文件夹中的主文件(即“index.ts”)可以有子组件(子模块)并可以导入它们
  • 您只能从“孩子”导入,而不能从“邻居”导入
  • 您不能跳过级别,并且只能从直接子级导入

或者,如果您更喜欢视觉效果,它只是一棵树:

图片描述

如果你需要在这个层级结构的不同层级之间共享某些代码(比如我们的发送反馈组件),你会立刻发现你违反了层级结构规则,因为无论你把它放在哪里,你都必须从父组件或邻居组件导入它。因此,它会被提取到“共享”层,并从那里导入。

图片描述

看起来像这样:

/my-page
  /shared
    send-feedback.ts
  /ui
    index.ts
    /header
      index.ts
      search-bar.ts
    /footer
      index.ts
Enter fullscreen mode Exit fullscreen mode

这样,UI 层(或任何适用该规则的层)就变成了一个树形结构,其中每个分支都独立于其他分支。现在,从这个包中提取任何内容都变得轻而易举:您只需将文件夹拖放到新位置即可。而且您可以确信,除了实际使用它的组件之外,UI 树中的任何其他组件都不会受到影响。您唯一可能需要额外处理的是“共享”层。

带有数据层的完整应用程序将如下所示:

图片描述

一些明确定义的层,完全封装且可预测。

/my-page
  /shared
    send-feedback.ts
  /data
    get-something.ts
    send-something.ts
  /ui
    index.ts
    /header
      index.ts
      search-bar.ts
    /footer
      index.ts
Enter fullscreen mode Exit fullscreen mode

React 建议不要嵌套

如果你阅读 React 文档中关于推荐项目结构的说明,你会发现 React 实际上并不建议过多的嵌套。官方建议是“考虑将单个项目中的嵌套文件夹限制在最多三到四个”。这个建议也适用于这种方法:如果你的包嵌套过多,这清楚地表明你可能需要考虑将其拆分成更小的包。根据我的经验,即使对于非常复杂的功能,3-4 级嵌套也足够了。

然而,包架构的美妙之处在于,您可以根据需要嵌套任意数量的包,而不受此限制——您永远无法通过相对路径引用另一个包,只能通过其名称引用。位于@project/change-setting-dialog路径中packages/change-settings-dialog或隐藏在 中的包,无论其物理位置如何,/packages/product/features/settings-page/change-setting-dialog都将被称为。@project/change-setting-dialog

Monorepo 管理工具

如果不稍微了解一下 Monorepo 管理工具,就无法谈论架构中多软件包的 Monorepo。最大的问题通常是内部的依赖管理。想象一下,如果你的某些 Monorepo 软件包使用了外部依赖,会是怎样的lodash体验。

/my-feature-one
  package.json // this one uses lodash@3.4.5
/my-other-feature
  package.json // this one uses lodash@3.4.5
Enter fullscreen mode Exit fullscreen mode

现在 lodash 发布了新版本 ,lodash@4.0.0你想把项目迁移到这个版本。你需要同时在所有地方更新它:你最不希望看到的就是一些软件包还在旧版本上,而另一些则在使用新版本。如果你还在使用npm旧版本yarn,那将是一场灾难:他们会在你的系统中安装多个副本(不是两个,而是多个lodash,这会导致安装和构建时间增加,并且你的软件包大小也会暴增。更不用说在整个项目中使用同一个库的两个不同版本来开发新功能的乐趣了。

npm如果您的项目要发布和开源,我不会谈论使用什么:可能像Lerna这样的东西就足够了,但这是一个完全不同的话题。

然而,如果你的仓库是私有的,事情就会变得更有趣。因为要使这种架构正常工作,你实际上只需要包的“别名”,仅此而已。也就是说,只需要YarnNpm通过工作区的概念提供的基本符号链接。它看起来像这样。你有一个“根”package.json文件,在其中声明工作区(即你的本地包)的位置:

{
  "private": true,
  "workspaces": ["packages/**"]
}
Enter fullscreen mode Exit fullscreen mode

然后,下次运行时,yarn install文件夹中的所有包都会变成“正式”的包,并可以通过其名称在项目中使用。这就是整个 monorepo 的设置!

至于依赖关系。如果几个包中有相同的依赖关系,会发生什么?

/packages
  /my-feature-one
    package.json // this one uses lodash@3.4.5
  /my-other-feature
    package.json // this one uses lodash@3.4.5
Enter fullscreen mode Exit fullscreen mode

当你运行时yarn install它会将该包“提升”到根目录node_modules

/node_modules
  lodash@3.4.5
/packages
  /my-feature-one
    package.json // this one uses lodash@3.4.5
  /my-other-feature
    package.json // this one uses lodash@3.4.5
Enter fullscreen mode Exit fullscreen mode

这与仅在根目录中声明的情况完全相同我的意思是,我可能会因此被互联网上的纯粹主义者(包括两年前的我自己)活埋:你不需要在本地包中声明任何依赖项。所有内容都可以直接放在根目录中。而且,本地包中的文件将只是非常轻量的文件,只需指定“name”和“main”字段。lodash@3.4.5package.jsonpackage.jsonpackage.jsonjson

设置管理更加容易,特别是对于刚开始的人来说。

React 项目结构规模化:最终概述

嗯,写了这么多。而且,这只是一个简短的概述:关于这个话题,还有很多话可以说!让我们至少回顾一下已经讨论过的内容:

分解是成功扩展 React 应用的关键。不要将你的项目视为一个单体“项目”,而要将其视为由多个独立的黑盒式“功能”组合而成,这些功能拥有各自公开的 API 供用户使用。这与“单体”与“微服务”的讨论其实是一样的。

Monorepo 架构非常适合这种情况。将您的功能提取到包中;以最适合您项目的方式组织您的包。

包内的层级结构非常重要,可以赋予包一定的结构。你至少应该包含“数据”层、“UI”层和“共享”层。你也可以根据需要添加更多层,但需要注意的是,它们之间的界限要清晰。

包的层次结构很酷。它使重构更容易,迫使你在层与层之间建立更清晰的界限,并且当包变得太大时,迫使你将包拆分成更小的包。

Monorepo 中的依赖管理是一个复杂的话题,但如果你的项目是私有的,则无需担心。只需在根目录的 package.json 中声明所有依赖项,并确保所有本地包不受这些依赖项的影响即可。

您可以在此示例 repo 中查看此架构的实现:https://github.com/developerway/example-react-project。这只是一个基本示例,用于演示文章中描述的原理,因此不要被只有一个 index.ts 的小包吓到:在真正的应用中,它们会大得多。

今天就到这里。希望你能将其中一些原则(甚至全部!)应用到你的应用中,并立即看到日常开发中的进步!✌🏼

...

最初发表于https://www.developerway.com。该网站还有更多类似的文章 😉

订阅时事通讯在 LinkedIn 上联系在 Twitter 上关注,以便在下一篇文章发布时立即收到通知。

鏂囩珷鏉ユ簮锛�https://dev.to/adevnadia/react-project-struct-for-scale-decomposition-layers-and-hierarchy-4ioa
PREV
成为全栈开发人员的终极路线图
NEXT
如何在 React 中不失理智地进行去抖动和节流