构建 React 项目的更好方法
2020年刚刚结束,对我个人而言,这是伟大的一年。我之前在之前的博文中对此进行了更详细的阐述。非常感谢所有阅读过这篇文章的人。我正在尝试通过写作分享更多我的生活,那篇博文就是一次尝试。想要了解更多更新,您可以通过电子邮件订阅本博客,或者在 Twitter 上关注我。
除此之外,还有几个人问我全职工作的地方。我的全职工作是DelightChat,我简直不能再好了。我擅长从零开始构建产品,并在团队中不受任何限制地共享知识。
由于对前端堆栈有了更好的控制,并且可以自由地尝试新技术,我对如何构建 React 项目以适应我们在此构建的复杂应用程序有了更好、更深入的理解。
而且由于大量的电子墨水已经浪费在“在 React 中执行 X”或“将 React 与技术 X 结合使用”等相对容易的选择上,因此我想写一下这个主题,这需要对 React 有更深入的理解并在生产环境中进行扩展使用。
介绍
简而言之,一个复杂的 React 项目应该像这样构建。虽然我在生产环境中使用 NextJS,但这种文件结构在任何 React 设置中都应该非常有用。
src
|---adapters
|---contexts
|---components
|---styles
|---pages
注意:在上述文件结构中,资产或静态文件应放置在 public *
框架的任何文件夹变体中。*
对于上述每个文件夹,让我们按优先顺序进行讨论。
1. 适配器
Adapters
是您的应用程序与外界的连接点。任何形式的 API 调用或 WebSocket 交互,例如与外部服务或客户端共享数据,都应该在适配器内部进行。
如果某些数据需要在所有适配器之间共享,例如,跨 AJAX(XHR)适配器共享 Cookie、基础 URL 和标头,则可以在 xhr 文件夹中初始化这些数据,然后将其导入到其他适配器中以供进一步使用。
该结构如下所示:
adapters
|---xhr
|---page1Adapter
|---page2Adapter
对于 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 ...
准备好基础文件(或多个文件)后,根据应用的复杂程度,为每个页面或每组功能创建一个单独的适配器文件。命名良好的函数可以很容易地理解每个 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 ...
但是这些适配器到底有什么用处呢?让我们在下一节中一探究竟。
2. 组件
虽然本节我们应该讨论上下文,但我首先想讨论一下组件。这是为了理解为什么在复杂的应用程序中需要(并且需要)上下文。
Components
是应用程序的命脉。它们负责应用程序的 UI,有时还负责业务逻辑以及需要维护的状态。
如果组件变得太复杂而无法用 UI 表达业务逻辑,最好将其拆分为单独的 bl.tsx 文件,并使用根 index.tsx 从中导入所有函数和处理程序。
这个结构看起来是这样的:
components
|---page1Components
|--Component1
|--Component2
|---page2Component
|--Component1
|---index.tsx
|---bl.tsx
在这种结构中,每个页面在组件内都有自己的文件夹,因此很容易弄清楚哪个组件影响什么。
限制组件的作用范围也很重要。因此,组件应该仅用于adapters
数据获取,为复杂的业务逻辑创建一个单独的文件,并且只关注 UI 部分。
// components/page1Components/Component1/index.tsx
import businessLogic from "./bl.tsx";
export default function Component2() {
const { state and functions } = businessLogic();
return {
// JSX
}
}
而 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);
}, [])
}
然而,所有复杂的应用程序都面临一个共同的问题:状态管理,以及如何在远程组件之间共享状态。例如,考虑以下文件结构:
components
|---page1Components
|--Component1
|---ComponentA
|---page2Component
|--ComponentB
如果在上面的例子中,某个状态必须在 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)
在上述情况下,由于情况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