51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

分享5个2021年的React体系结构最佳实践

毫无疑问,React彻底改变了我们构建用户界面的方式。它易于学习,极大地方便了创建可重用组件,从而为您的网站提供一致的外观。

但是,由于React只负责应用程序的视图层,因此它不执行任何特定的体系结构(例如MVC或MVVM)。随着您的React项目的发展,这可能很难保持您的代码库井井有条。

在9elements上,我们的旗舰产品之一是PhotoEditorSDK-完全可自定义的照片编辑器,可以轻松集成到您的HTML5,iOS或Android应用程序中。PhotoEditorSDK是面向开发人员的大型React应用程序。它需要高性能,小巧的外观,并且在样式(尤其是主题)方面需要非常灵活。

在PhotoEditorSDK的许多迭代过程中,我和我的团队都掌握了组织大型React应用程序的许多最佳实践,在本文中,我们希望与您分享其中的一些最佳实践。

1.目录布局

最初,组件的样式和代码是分开的。所有样式都保存在共享的CSS文件中(我们使用SCSS进行预处理)。实际组件(在本例中为FilterSlider)已与样式分离:

├── components
│   └── FilterSlider
│       ├──  __tests__
│       │   └── FilterSlider-test.js
│       └── FilterSlider.jsx
└── styles
    └── photo-editor-sdk.scss

在多次重构中,我们发现这种方法的伸缩性不是很好。将来,我们的组件需要在多个内部项目之间共享,例如SDK和我们当前正在开发的实验性文本工具。因此,我们切换到以组件为中心的文件布局:

components
    └── FilterSlider
        ├── __tests__
        │   └── FilterSlider-test.js
        ├── FilterSlider.jsx
        └── FilterSlider.scss

想法是,所有属于组件的代码(例如JavaScript,CSS,资产,测试)都位于单个文件夹中。这样可以很容易地将代码提取到npm模块中,或者如果您急于将其简单地与另一个项目共享该文件夹。

导入组件

此目录结构的缺点之一是,导入组件需要您导入完全限定的路径,如下所示:

import FilterSlider from 'components/FilterSlider/FilterSlider'

但是我们真正想写的是这样的:

import FilterSlider from 'components/FilterSlider'

要解决此问题,您可以创建一个index.js并立即导出默认值:

export { default } from './FilterSlider';

另一个解决方案更广泛一些,但是它使用了Node.js标准解析机制,使其坚如磐石且面向未来。我们要做的就是package.json在文件结构中添加一个文件:

components
    └── FilterSlider
        ├── __tests__
        │   └── FilterSlider-test.js
        ├── FilterSlider.jsx
        ├── FilterSlider.scss
        └── package.json

在内package.json,我们使用main属性将我们的入口点设置为组件,如下所示:

{
  "main": "FilterSlider.jsx"}

有了这个添加,我们可以导入一个像这样的组件:

import FilterSlider from 'components/FilterSlider'

2. JavaScript中的CSS

样式,尤其是主题,一直是一个问题。如上所述,在应用程序的第一次迭代中,我们有一个很大的CSS(SCSS)文件,所有类都存在其中。为避免名称冲突,我们使用了全局前缀并遵循BEM约定来制作CSS规则名称。当我们的应用程序增长时,这种方法的伸缩性不是很好,因此我们寻找了替代方法。首先,我们评估了CSS模块,但当时它们存在一些性能问题。另外,通过webpack的Extract Text插件提取CSS效果也不佳(尽管在撰写本文时应该可以)。此外,这种方法严重依赖于webpack,并且使测试非常困难。

接下来,我们评估了最近出现的其他一些CSS-in-JS解决方案:

  • 样式化的组件:在最大的社区中最受欢迎的选择

  • EmotionJS:最热的竞争对手

  • Linaria:零运行时间解决方案

选择这些库之一很大程度上取决于您的用例:

  • 您是否需要该库将已编译的CSS文件吐出进行生产?EmotionJS和Linaria可以做到!Linaria甚至不需要运行时。它通过CSS变量将props映射到CSS,CSS变量排除了IE11的支持-但是谁仍然需要IE11?

  • 是否需要在服务器上运行?这对于所有库的最新版本都没有问题!

对于目录结构,我们希望将所有样式放在styles.js

export const Section = styled.section`
  padding: 4em;
  background: papayawhip;`;

这样,纯粹的前端人员还可以编辑某些样式而无需处理React,但是他们必须学习最少的JavaScript以及如何将props映射到CSS属性:

components
    └── FilterSlider
        ├── __tests__
        │   └── FilterSlider-test.js
        ├── styles.js
        ├── FilterSlider.jsx
        └── index.js

从HTML整理您的主要组件文件是一个好习惯。

React组件

当您开发高度抽象的UI组件时,有时很难分开关注点。在某些时候,您的组件将需要模型中的特定领域逻辑,然后事情变得一团糟。在以下各节中,我们将向您展示用于烘干组件的某些方法。以下技术在功能上有重叠,并且为体系结构选择合适的技术更倾向于样式,而不是基于事实。但是,让我先介绍一下用例:

  • 我们必须引入一种机制来处理对登录用户具有上下文意识的组件。

  • 我们必须渲染一个包含多个可折叠<tbody>元素的表。

  • 我们必须根据不同的状态显示不同的组件。

在以下部分中,我将显示上述问题的不同解决方案。

3.定制挂钩

有时,您必须确保仅当用户登录到您的应用程序时才显示React组件。最初,您将在渲染时进行一些完整性检查,直到发现自己在重复很多次。在使该代码干燥的任务上,您迟早必须编写自定义的钩子。不要害怕:这并不难。看下面的例子:

import { useEffect } from 'react';import { useAuth } from './use-auth-from-context-or-state-management.js';import { useHistory } from 'react-router-dom';function useRequireAuth(redirectUrl = "/signup") {
  const auth = useAuth();
  const history = useHistory();
  // If auth.user is false that means we're not
  // logged in and should redirect.
  useEffect(() => {
    if (auth.user === false) {
      history.push(redirectUrl);
    }
  }, [auth, history]);
  return auth;}

useRequireAuth钩子将检查是否有用户登录否则重定向到其他页面。useAuth挂钩中的逻辑可以通过上下文或状态管理系统(例如MobX或Redux)提供。

4.Function as Children

创建可折叠的表行不是一项非常简单的任务。您如何呈现折叠按钮?桌子未折叠时,我们将如何显示子代?我知道使用JSX 2.0可以轻松得多,因为您可以返回数组而不是单个标签,但是我将在此示例中进行扩展,因为它说明了作为子模式的功能的良好用例。想象一下下表:

export default function Table({ children }) {
  return (
    <table>
      <thead>
        <tr>
          <th>Just a table</th>
        </tr>
      </thead>
      {children}
    </table>
  );}

和一个可折叠的桌子主体:

import { useState } from 'react';export default function CollapsibleTableBody({ children }) {
  const [collapsed, setCollapsed] = useState(false);
  const toggleCollapse = () => {
    setCollapsed(!collapsed);
  };
  return (
    <tbody>
      {children(collapsed, toggleCollapse)}
    </tbody>
  );}

您可以通过以下方式使用此组件:

<Table>
  <CollapsibleTableBody>
    {(collapsed, toggleCollapse) => {
      if (collapsed) {
        return (
          <tr>
            <td>
              <button onClick={toggleCollapse}>Open</button>
            </td>
          </tr>
        );
      } else {
        return (
          <tr>
            <td>
              <button onClick={toggleCollapse}>Closed</button>
            </td>
            <td>CollapsedContent</td>
          </tr>
        );
      }
    }}
  </CollapsibleTableBody></Table>

您只需将功能作为子代传递,即可在父组件中调用。您可能还已经将这种技术称为"渲染回调",或者在特殊情况下称为"渲染道具"。

5.Render Props

"Render Props"一词是Michael提出的,他建议高阶组件模式可以100%的时间用带有"渲染道具"的常规组件替换。这里的基本思想是所有React组件都是函数,并且函数可以作为prop传递。那么为什么不通过props传递React组件呢?

以下代码试图概括如何从API获取数据。(请注意,此示例仅用于演示目的。在实际项目中,您甚至会将此获取逻辑抽象为一个useFetch钩子,以使其与UI进一步分离。)这是代码:

import { useEffect, useState } from "react";export default function Fetch({ render, url }) {
  const [state, setState] = useState({
    data: {},
    isLoading: false
  });
  useEffect(() => {
    setState({ data: {}, isLoading: true });
    const _fetch = async () => {
      const res = await fetch(url);
      const json = await res.json();
      setState({
        data: json,
        isLoading: false,
      });
    }
    _fetch();
  }, https%3A%2F%2Feditor.xxx.com);
  return render(state);}

如您所见,有一个名为的属性render,这是在渲染过程中调用的函数。在其内部调用的函数将完整状态作为其参数,并返回JSX。现在查看以下用法:

<Fetch
  url="https://api.github.com/users/imgly/repos"
  render={({ data, isLoading }) => (
    <div>
      <h2>img.ly repos</h2>
      {isLoading && <h2>Loading...</h2>}

      <ul>
        {data.length > 0 && data.map(repo => (
          <li key={repo.id}>
            {repo.full_name}
          </li>
        ))}
      </ul>
    </div>
  )} />

如您所见, dataisLoading参数是从状态对象中解构的,可用于驱动JSX的响应。在这种情况下,只要没有兑现承诺,就会显示"正在加载"标题。由您决定将状态的哪些部分传递给渲染道具,以及如何在用户界面中使用它们。总体而言,这是提取常见UI行为的非常强大的机制。上面描述的作为子模式的功能与该属性基本上是相同的模式children

Protip:由于render prop模式是功能作为子模式的泛化,因此没有什么可以阻止您在一个组件上具有多个render prop。例如,一个Table组件可以为标题获取一个渲染道具,然后为主体获取另一个渲染道具。

总结

我希望您喜欢这篇关于体系结构React模式的文章。如果您在本文中缺少某些内容(肯定有更多最佳实践),或者如果您想与我们取得联系,请留言。


赞(0)
未经允许不得转载:工具盒子 » 分享5个2021年的React体系结构最佳实践