React 初学者应该避免的 5 种代码

提交之前,请务必仔细检查对这些内容的拉取请求

照片由Priscilla Du PreezUnsplash拍摄

作为无数不同背景(包括训练营和其他非传统教育途径)的前端初学者的导师,我观察到了许多不同的 React 编码方法。React 的部分美妙之处在于,相对于 Angular 和 Vue 等框架而言,它通常是无限制的的。这使得它足够灵活,可以插入用不同框架编写的大量现有代码库。此外,它还为不同的用例激发了大量的社区库,从而推动整个行业向前发展。

然而,与惯例无关、配置繁重的生态系统的缺点是,它为初学者留下了大量偏离最佳实践的空间。以下是我在审查新 React 开发人员的拉取请求时发现的五个巧妙的实践——可以称之为代码味道。

应该注意的是,在这种情况下,“代码味道”只是我注意到的一种模式,它引导我对其进行进一步审查。这并不意味着这些模式是不正确的,在好的软件工程中没有绝对的规则。在特殊情况下,考虑到适当的上下文,这些代码气味中的每一种都可能有意义。话虽如此,如果你发现自己在使用这些模式,你应该停下来问问自己,“这真的有必要吗?”

1. 可变变量

在过去的五年里,我仅使用一次可变变量——就像我说的,每个最佳实践都有一个例外。可变变量是可以在整个代码执行过程中重新分配的变量。JavaScript 中的这些变量是 letvar 。如果你使用 ES6 语法,你可能永远不想使用 var ,但我仍然发现许多 React 初学者使用 let .

在审查拉取请求时,看到任何不是 const React 中的变量的变量告诉我,开发人员可能最熟悉过程设计模式。所述累加器模式是利用一个可变变量,我看到使用许多初学者程序设计图案的一个这样的例子。几乎所有使用可变变量的设计模式都可以替换为等效的函数式编程。

blog.devgenius.io

  1. 程序模式

如前所述,程序模式通常表示使用可变变量。每当我看到以下任何一个操作符时,我都能闻到一种程序模式的形成:

  • for 循环
  • while 循环
  • forEach 操作
  • push 到数组

这些通常表示 React 组件内部的副作用,这在纯函数式编程中的做法不是很理想,React 真的是为了在函数式和声明式范式中使用。有关 JavaScript 函数式编程的更多信息,请参见下文:

https://betterprogramming.pub/functional-programming-in-javascript-introduction-and-practical-examples-d268e44395b2

作为这些代码味道的替代方案,你可以经常使用 mapreducefind 、 和 filter 数组方法代替。

3. 字符串字面量

字符串文字是代码中任何“匿名字符串”或任何未作为某种类型的变量或数据结构存储在内存中的字符串。字符串文字使你的代码在重构时变得更加 脆弱 ,并且无法支持 国际化 。许多初学者在早期的整个代码中都使用字符串文字,因为他们还没有遇到最终让他们感到痛苦的情况。

具体参考以下代码示例:

import React from 'react';

export const Badge = (props) => {
 return (
    <div style={
      {backgroundColor: props.status === 'active' ? 'green' : 'red'}
    }>
      Status: {props.status}
    </div>
   );
};

图 1.1 — React 组件中用于有条件地呈现文本样式的字符串文字。

图 1.1 演示了一个 React 组件,根据 status prop 是否等于字符串文字 'active' 有条件地渲染 Badge 组件的背景。因为 status prop 也用于将文本渲染到屏幕上,所以假设用户需求可能会在某个时候发生变化并且他们宁愿看到“就绪”而不是“活跃”这个词是很正常的。在这种情况下,开发人员必须检查App中任何类型的业务逻辑中所使用字符串文字 'active' 的任何地方。如果他们错过了这个特定的三元表达式,那么“准备好”这个词 显示在屏幕上,但它会有一个红色的背景——这不是他们想要的用户体验。

如果他们改为重构该代码以使用内存中的单个引用点,如图 1.2 所示,那么他们已经最大限度地减少了未来的重构工作。如果用户回来并决定再次从“就绪”更改为“完成”,那么开发人员不再需要在代码中到处寻找对字符串“就绪”的引用。相反,他们可以简单地更改 status.js 文件中 status 对象的 active 属性值,然后就可以了!

export const status = {
  active: 'Ready',
  inactive: 'Inactive'
}
import React from 'react';
import { status } from './status';

export const Badge = (props) => {
 return (
    <div style={
      {backgroundColor: props.status === status.active ? 'green' : 'red'}
    }>
      Status: {props.status}
    </div>
   );
};

图 1.2 — 重构徽章组件以消除字符串文字。

尽量减少字符串文字的使用才能使你的代码更有资格翻译成其他语言,因为你可以调用语言文件以返回对适当字符串的引用。例如,如果 Badge 组件将语言代码作为其 props 之一,那么你可以重构组件中的标签,使其不再使用字符串文字。这在下图 1.3 中的第 9 行进行了演示。现在,为了支持更多语言,你所要做的就是更新你的 labels 对象,并且立即在组件中呈现新语言。

import React from 'react';
import { status, labels } from './status';

export const Badge = (props) => {
 return (
    <div style={
      {backgroundColor: props.status === status.active ? 'green' : 'red'}
    }>
      {labels.statusPrompt[props.language]}: {props.status}
    </div>
   );
};
export const status = {
  active: 'Ready',
  inactive: 'Inactive'
}

export const labels = {
  statusPrompt: {
    'en-US': 'Status', 
    'fr-FR': 'Statut',
  },
  welcomeMessage: {
    'en-US': 'Welcome to this website!', 
    'fr-FR': 'Bienvenue sur ce site!',
  }
}

图 1.3 — 重构我们的代码以实现国际化。

4.直接进行DOM操作

我经常看到 React 初学者尝试在 React 功能组件的主体中使用普通的 JavaScript 来直接访问 HTML DOM 元素。很多时候,他们使用这种方法是因为他们就是这样学习 JavaScript 的,或者他们在 Stack Overflow 上找到了一篇与 React 无关的帖子,为他们提供了当时所需解决方案——从他们的角度来看,它确实有效!

document.getElementById('virtual-div')

但是,由于 React 使用虚拟 DOM,因此无法保证你在查询时尝试访问的DOM 元素会存在。它可能尚未安装,或者可能正在重新渲染。无论哪种方式,在组件生命周期的整个过程中,你对DOM 元素所做的任何更改都将丢失。

你不需要直接对DOM进行操作,而是需要使用 useRef Hook 来创建对要操作的 DOM 元素加以引用,以便你可以在组件重新渲染的整个过程中继续访问它。在官方文件React网站的 useRef Hook文档是一个很好的的参考,详情:

5. 大型组件文件

这可能更像是一种架构味道而不是代码味道,但无论如何都值得一提。我见过许多全新的工程师,他们只顾自己单独编写代码,从未与其他工程师团队一起编写代码。在这种情况下,开发人员用一两个非常大的组件编写整个应用程序的情况并不少见。默认情况下,他们需要将代码抽象和组合成更小、更易消化且更易于维护的组件。毕竟,没有其他人需要阅读代码,对吧?

这对新工程师来说是一个非常重要的启示:我们不是为计算机编写代码。我们为下一位出现的工程师编写代码。

即使你是一个团队,下一个出现的工程师也是你未来的自己。有时,在未来数月或数年,你会回顾在单个组件中整合在一起的代码,并想,“这又是什么意思?”

有时,这种架构气味并不那么强烈。相反,我可能会看到开发人员编写了一些他们根本不想创建的新组件的代码——也许现在只是感觉有点过头了。但是,如果你随着时间的推移对单个组件进行大量的细微更改,最终你将得到一个大组件。腻甚至可能会欺骗自己在另一个组件中编写一个组件,而你全然没有意识到这一点。

import React from 'react';

export const Grid = (props) => {
  const renderRow = (rowData) => (
    <div className="row">
      <div className="col">
        {rowData.title}
      </div>
      <div className="col">
        {rowData.description}
      </div> 
      <div className="col">
        {rowData.date}
      </div> 
    </div>
   )
  
  return (
    <div className="grid__container">
      {props.data.map((datum) => renderRow(datum))}
    </div>
  )
};

图 2.1 — 一个可能比实际需要大的组件文件。

我以前见过这样的场景。开发人员心里想,“我不想为一系列数据中的每一行重写所有代码行,所以我只是将其抽象为一个快速函数。” 他们没有意识到他们实际上只是创建了一个新组件。 rowData ,他们已经针对所创建的参数 renderRow 创造了与传统的功能部件 props 参数大致相同的 rowData 。出现这种情况时,我要求开发人员将该代码放在自己的组件文件中,以便将来可以单独维护和迭代。

我调查了机构中的几位高级工程师,我们得出了以下经验法则。如果你的组件文件在 100 到 200 行代码之间,那么你几乎需要重构代码使其更小。如果你的代码超过 200 行,你可能需要考虑以某种方式使文件更小。你的文件可能太大有两个主要原因:

  1. 当在单个组件中放置了太多 UI 元素。在这种情况下,你可以简单地将一些 UI 抽象为更小的组件,然后在父组件中引用它们。
  2. 当在组件中放置了大量业务逻辑。一般来说,我尽量让我的工程师避免这种情况。写在功能组件主体中的业务逻辑往往更难测试。如果它位于你的功能组件中,那么测试业务逻辑的唯一真正方法是将组件安装在您的测试文件中并模拟用户交互以查看是否获得所需的 UI 结果。或者,你可以将任何业务逻辑完全从组件中提取出来,并将它们放入实用程序文件或作为状态管理逻辑的一部分(例如 Redux)。我通常更喜欢后一种解决方案。它允许编写简单的纯函数,可以轻松地使用单元测试进行测试。这里有另一个经验法则:最小化表示层中的业务逻辑!

如果我在 React 应用的表现层(组件)看到很多业务逻辑,即使组件文件不超过 200 行代码,我也倾向于要求工程师重构他们的代码并将业务逻辑移出组件。大多数情况下,如果你使用纯函数进行编码,这就变得很容易。然而,这只是一个经验法则,并不是总能行得通的。

结论

再强调一次,这些“味道”并不是都是不好的。他们只是倾向于让我在审查拉取请求时查看代码两次。如果我发现自己在使用它们,我一定会为我的审阅者留下评论,说明使用的原因。如果你有其他合理的理由使用它们,欢迎评论(GitHub 代码的链接会很棒)。

原文作者 Jason Lee Hodges
原文链接https://betterprogramming.pub/5-code-smells-react-beginners-should-avoid-480c97799162

推荐阅读
相关专栏
前端与跨平台
90 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。