构建 React 项目的更好方法

2025-05-28

构建 React 项目的更好方法

照片由 Ferenc Almasi 在 Unsplash 上拍摄

2020年刚刚结束,对我个人而言,这是伟大的一年。我之前在之前的博文中对此进行了更详细的阐述非常感谢所有阅读过这篇文章的人。我正在尝试通过写作分享更多我的生活,那篇博文就是一次尝试。想要了解更多更新,您可以通过电子邮件订阅本博客,或者在 Twitter 上关注

除此之外,还有几个人问我全职工作的地方。我的全职工作是DelightChat,我简直不能再好了。我擅长从零开始构建产品,并在团队中不受任何限制地共享知识。

由于对前端堆栈有了更好的控制,并且可以自由地尝试新技术,我对如何构建 React 项目以适应我们在此构建的复杂应用程序有了更好、更深入的理解。

而且由于大量的电子墨水已经浪费在“在 React 中执行 X”或“将 React 与技术 X 结合使用”等相对容易的选择上,因此我想写一下这个主题,这需要对 React 有更深入的理解并在生产环境中进行扩展使用。

介绍

简而言之,一个复杂的 React 项目应该像这样构建。虽然我在生产环境中使用 NextJS,但这种文件结构在任何 React 设置中都应该非常有用。

src
|---adapters
|---contexts
|---components
|---styles
|---pages
Enter fullscreen mode Exit fullscreen mode

注意:在上述文件结构中,资产或静态文件应放置在 public *框架的任何文件夹变体中。*

对于上述每个文件夹,让我们按优先顺序进行讨论。

1. 适配器

Adapters是您的应用程序与外界的连接点。任何形式的 API 调用或 WebSocket 交互,例如与外部服务或客户端共享数据,都应该在适配器内部进行。

如果某些数据需要在所有适配器之间共享,例如,跨 AJAX(XHR)适配器共享 Cookie、基础 URL 和标头,则可以在 xhr 文件夹中初始化这些数据,然后将其导入到其他适配器中以供进一步使用。

该结构如下所示:

adapters
|---xhr
|---page1Adapter
|---page2Adapter
Enter fullscreen mode Exit fullscreen mode

对于 axios,你可以使用它axios.create来创建一个基础适配器,并导出这个初始化的实例,或者为 get、post、patch 和 delete 创建不同的函数来进一步抽象它。如下所示:

// adapters/xhr/index.tsx

import Axios from "axios";

function returnAxiosInstance() {
  return Axios.create(initializers);
}

export function get(url){
  const axios = returnAxiosInstance();
  return axios.get(url);
}

export function post(url, requestData){
  const axios = returnAxiosInstance();
  return axios.post(url, requestData);
}

... and so on ...
Enter fullscreen mode Exit fullscreen mode

准备好基础文件(或多个文件)后,根据应用的复杂程度,为每个页面或每组功能创建一个单独的适配器文件。命名良好的函数可以很容易地理解每个 API 调用的作用及其应该实现的功能。

// adapters/page1Adapter/index.tsx

import { get, post } from "adapters/xhr";
import socket from "socketio";

// well-named functions
export function getData(){
  return get(someUrl);
}

export function setData(requestData){
  return post(someUrl, requestData);
}

... and so on ...
Enter fullscreen mode Exit fullscreen mode

但是这些适配器到底有什么用处呢?让我们在下一节中一探究竟。

2. 组件

虽然本节我们应该讨论上下文,但我首先想讨论一下组件。这是为了理解为什么在复杂的应用程序中需要(并且需要)上下文。

Components是应用程序的命脉。它们负责应用程序的 UI,有时还负责业务逻辑以及需要维护的状态。

如果组件变得太复杂而无法用 UI 表达业务逻辑,最好将其拆分为单独的 bl.tsx 文件,并使用根 index.tsx 从中导入所有函数和处理程序。

这个结构看起来是这样的:

components
|---page1Components
        |--Component1
        |--Component2
|---page2Component
        |--Component1
               |---index.tsx
               |---bl.tsx
Enter fullscreen mode Exit fullscreen mode

在这种结构中,每个页面在组件内都有自己的文件夹,因此很容易弄清楚哪个组件影响什么。

限制组件的作用范围也很重要。因此,组件应该仅用于adapters数据获取,为复杂的业务逻辑创建一个单独的文件,并且只关注 UI 部分。

// components/page1Components/Component1/index.tsx

import businessLogic from "./bl.tsx";

export default function Component2() {

  const { state and functions } = businessLogic();

  return {
    // JSX
  }
}
Enter fullscreen mode Exit fullscreen mode

而 BL 文件仅导入数据并返回

// components/page1Components/Component1/bl.tsx

import React, {useState, useEffect} from "react";
import { adapters } from "adapters/path_to_adapter";

export default function Component1Bl(){
  const [state, setState] = useState(initialState);

  useEffect(() => {
    fetchDataFromAdapter().then(updateState);
  }, [])
}

Enter fullscreen mode Exit fullscreen mode

然而,所有复杂的应用程序都面临一个共同的问题:状态管理,以及如何在远程组件之间共享状态。例如,考虑以下文件结构:

components
|---page1Components
        |--Component1
               |---ComponentA
|---page2Component
        |--ComponentB
Enter fullscreen mode Exit fullscreen mode

如果在上面的例子中,某个状态必须在 ComponentA 和 B 之间共享,则它必须通过所有中间组件传递,并且还要传递给任何想要与该状态交互的其他组件。

为了解决这个问题,有几种解决方案可供选择,例如 Redux、Easy-Peasy 和 React Context,它们各有优缺点。通常,React Context 应该“足够好”地解决这个问题。我们将所有与 context 相关的文件存储在 中contexts

3. 上下文

contexts文件夹是一个最小文件夹,仅包含必须在这些组件之间共享的状态。每个页面可以有多个嵌套上下文,每个上下文仅向下传递数据,但为了避免复杂性,最好只有一个上下文文件。其结构如下所示:

contexts
|---page1Context
        |---index.tsx (Exports consumers, providers, ...)
        |---Context1.tsx (Contains part of the state)
        |---Context2.tsx (Contains part of the state)
|---page2Context
        |---index.tsx (Simple enough to also have state)
Enter fullscreen mode Exit fullscreen mode

在上述情况下,由于情况page1可能略微复杂,我们通过将子上下文作为子级传递给父级来允许嵌套上下文。不过,通常情况下,一个index.tsx包含状态并导出相关文件的文件就足够了。

我不会深入讨论 React 状态管理库的具体实现,因为它们各有优缺点,各有特色。因此,我建议你先阅读一下你打算使用的库的教程,了解它们的最佳实践。

上下文允许导入,adapters以便获取并响应外部效果。对于 React Context,提供程序会在页面内部导入,以便在所有组件之间共享状态,并useContext在这些页面内部使用类似的东西components来利用这些数据。

接下来讨论最后一个主要拼图pages

4. 页面

在本文中,我不想偏向某个框架,但通常来说,为路由级组件设置一个专门的文件夹是一种很好的做法。Gatsby 和 NextJS 强制要求将所有路由放在一个名为 的文件夹中pages。这是一种定义路由级组件的可读性较高的方法,在 CRA 生成的应用程序中效仿这种方法也能提高代码的可读性。

集中式路由位置还能帮助您利用大多数 IDE 的“跳转文件”功能,只需按住 Cmd 或 Ctrl 键并单击导入即可跳转至文件,从而快速浏览代码,并清晰地了解各个部分的位置。它还在pages和之间建立了清晰的区分层次components,页面可以导入组件进行显示,而无需执行任何其他操作,甚至无需执行业务逻辑。

但是,您可以在页面内部导入 Context Provider,以便子组件可以使用它。或者,对于 NextJS,您可以编写一些服务器端代码,使用 getServerSideProps 或 getStaticProps 将数据传递给组件。

5. 风格

最后,我们来谈谈样式。虽然我常用的方法是使用像 Styled-Components 这样的 CSS-in-JS 解决方案将样式嵌入到 UI 中,但有时在 CSS 文件中设置一组全局样式也会很有帮助。简单的旧式 CSS 文件更易于跨项目共享,并且还可以影响 styled-components 无法访问的组件(例如第三方组件)的 CSS。

因此,您可以将所有这些 CSS 文件存储在styles文件夹中,并从任何您希望的地方自由导入或链接到它们。

以上就是我的想法。如果你想讨论一下,或者对如何改进有任何建议,欢迎随时给我发邮件!

如需了解更多更新信息,您可以通过电子邮件订阅此博客,或在 Twitter 上关注

文章来源:https://dev.to/thewritingdev/a-better-way-to-struct-react-projects-4ci6
PREV
Docker 精通:面向初学者和专业人士的综合指南
NEXT
2025 年开发人员必备 SEO 清单