ESLint 有一套全面的 JavaScript 代码规则,涵盖风格选择并防止常见错误。单独使用 ESLint 可以提升你的项目,但是有一些 ESLint 插件可用于添加特定于 React 的规则,这将帮助你编写可靠的 React 应用程序。

在这篇文章中,我们将讨论这些 ESLint 规则和插件,包括它们适用于 Hooks 的情况。这里有一些快速链接供您跳转:

  • React Hooks 规则 ( eslint-plugin-react-hooks)
  • react-hooks/rules-of-hooks
  • react-hooks/exhaustive-deps
  • 反应规则 ( eslint-plugin-react)
  • react/button-has-type
  • react/prop-types
  • react/require-default-props
  • react/no-array-index-key
  • react/react-in-jsx-scope
  • react/jsx-uses-react
  • react/display-name
  • react/no-danger-with-children
  • react/jsx-no-bind

React Hooks 规则 ( eslint-plugin-react-hooks)

这个插件只包含两个规则,但它们对于避免使用 Hooks编写函数组件时的常见陷阱至关重要。

反应钩子/钩子规则

此规则强制组件在使用 Hooks 时遵循Hooks 规则。这些规则在React 文档中有详细讨论,但是在使用 Hooks 时必须遵循两个规则:

  1. 钩子只能从组件的顶级代码中调用。这真正意味着 Hooks 不应该被有条件地调用——它们应该在每次渲染时以相同的顺序被调用,以避免问题和细微的错误
  2. 只能从函数组件或另一个 Hook 调用 Hook
  1. 自定义 Hooks 通常将来自内置的甚至其他自定义 Hooks 的行为组合在一起

在默认配置中,违反此规则将导致错误,导致 lint 检查失败。

反应钩子/详尽的deps

此规则强制执行有关传递给 Hooks 的依赖数组内容的某些规则,例如useEffect、useCallback和useMemo。一般来说,效果、回调或记忆值计算中引用的任何值都必须包含在依赖数组中。如果未正确完成,可能会导致状态数据过时或无限渲染循环等问题。

这条规则善于发现潜在的依赖相关的 bug,但也有一些限制:

  • 此规则不会检查具有依赖关系数组的自定义 Hook。它仅适用于内置 Hooks
  • 只有当它是一个静态值数组时,该规则才能正确检查依赖关系。如果使用了对另一个数组的引用,或者将另一个数组散布到其中,则规则将发出无法确定依赖关系的警告

这条规则有些争议。GitHub 上有几个很长的问题线程,但 React 团队在征求和整合反馈方面做得很好。在默认配置中,违反此规则将被视为警告。

这条规则的细节可以单独占据整篇文章。要深入了解此规则以及如何正确使用它,请参阅 LogRocket 博客上的了解 React 详尽的 linting 警告文章。

反应规则 ( eslint-plugin-react)

该插件包含更多特定于 React 核心的规则(撰写本文时为 100 条规则)。大多数规则涵盖一般的 React 实践,而其他规则则涵盖与 JSX 语法相关的问题。让我们来看看一些更有用的。

反应/按钮有类型

出于可访问性的原因,组件中的大多数不是指向另一个 URL 的简单链接的可点击元素都应该实现为按钮。type一个常见的错误是在这些按钮不用于提交表单时忽略这些按钮的属性。

指定no 时type,按钮默认为submit. 这可能会导致从form元素下降的按钮出现问题。单击表单内的此类按钮将导致可能不需要的表单提交。

不打算提交表单的操作按钮应type具有button.

该规则强制要求所有按钮都明确地具有一个type属性——即使是那些打算用作提交按钮的按钮。通过明确,可以避免无意的提交,并且代码的意图是明确的。

反应/道具类型

要求所有 React 组件都在PropTypes声明中描述其 props。这些检查只会在开发模式下抛出错误,但可以帮助捕获由错误的 props 传递给组件引起的错误。

如果您的项目使用 TypeScript,则通过在描述它们的组件 props 中添加类型注释也可以满足此规则。

Dillion Megida的比较 React 应用程序中的 TypeScript 和 PropTypes详细介绍了这两种方法。

反应/要求默认道具

根据组件的不同,一些道具可能是必需的,而另一些则是可选的。如果一个可选的 prop 没有传递给组件,它将是undefined. 这可能是意料之中的,但如果未检查该值,则可能会引入错误。

此规则要求defaultProps在组件的声明中为每个可选 prop 赋予一个默认值。此默认值可以显式设置为,null或者undefined如果这是组件所期望的。

对于函数组件,可以使用两种不同的策略来检查默认道具:

默认道具

此策略期望函数组件具有具有defaultProps默认值的对象。


超过 20 万开发人员使用 LogRocket 来创造更好的数字体验了解更多 →


const MyComponent = ({ action }) => { ... } MyComponent.propTypes = {  Action: PropTypes.string; }; MyComponent.defaultProps = {  action: 'init' };


默认参数

此策略期望在函数声明中指定默认值,使用 JavaScript 的内置默认值语法。


const MyComponent = ({ action = 'init' }) => { ... } MyComponent.propTypes = {  Action: PropTypes.string; };


如果您使用该defaultArguments策略,则不应该有defaultProps对象。如果有,则此规则将失败。

反应/无数组索引键

在 React 中渲染项目列表时,我们通常调用map一个数组,映射函数返回一个组件。为了跟踪列表中的每个项目,React 需要这些组件有一个keyprop。

渲染列表的一个常见缺陷是使用数组索引作为键。这可能会导致不必要的甚至不正确的渲染。React 文档不建议这种做法,因为它可能导致问题(还有关于如何使用密钥的更详细的讨论)。键应该是列表中该项目的唯一标识符,不会更改,例如数据库行中的主键值。

此规则确保不将数组索引用作键。

react/react-in-jsx-scope

考虑这个简单的 React 组件:


const Greeter = ({ name }) => <div>Hello {name}!</div>;


该React对象根本没有被引用。但是,React仍然需要导入,否则会遇到错误。这是由于 JSX 的转译过程。分享两款超实用的工具箱,图片处理、音频、文字识别、解压缩等聚合软件!浏览器不理解 JSX,因此在构建过程中(通常使用 Babel 或 TypeScript 等工具),JSX 元素被转换为有效的 JavaScript。

这会生成 JavaScript 代码调用React.createElement来代替 JSX 元素。上面的组件可能会被转译成这样的东西:


const Greeter = ({ name }) => React.createElement("div", null, "Hello ", name, "!");


对这里的引用React是为什么React仍然必须导入。此规则确保所有带有 JSX 标记的文件(甚至不一定是 React 组件)都React在范围内(通常通过importorrequire调用)。

反应/jsx 使用反应

始终导入 React 是正确编译所必需的,但是当 ESLint 查看文件时,它仍然是 JSX,所以它不会React在任何地方看到引用。如果项目正在使用该no-unused-vars规则,则会导致错误,因为React已导入但未在任何地方使用。


来自 LogRocket 的更多精彩文章:

  • 不要错过来自 LogRocket 的精选时事通讯The Replay
  • 了解LogRocket 的 Galileo 如何消除噪音以主动解决应用程序中的问题
  • 使用 React 的 useEffect优化应用程序的性能
  • 在多个 Node 版本之间切换
  • 了解如何使用 AnimXYZ 为您的 React 应用程序制作动画
  • 探索 Tauri,一个用于构建二进制文件的新框架
  • 比较NestJS 与 Express.js

此规则捕获这种情况并防止导入no-unused-vars失败。React

反应/显示名称

为了正确的调试输出,所有 React 组件都应该有一个显示名称。在许多情况下,这不需要任何额外的代码。如果组件是命名函数,则显示名称将是函数的名称。在以下示例中,组件的显示名称将为MyComponent.

  • const MyComponent = () => { … }
  • const MyComponent = function() { return …; }
  • export default function MyComponent() { return …; }

在某些情况下,自动显示名称会丢失。这通常是当组件声明被另一个函数或更高阶组件包装时,如下面的两个示例:

  • const MyComponent = React.memo(() => { … });
  • const MyComponent = React.forwardRef((props, ref) => { … });

该名称绑定到andMyComponent返回的新“外部”组件。组件本身现在没有显示名称,这将导致此规则失败。memo``forwardRef

当出现这些情况时,可以通过displayName属性手动指定显示名称以满足规则:


const MyComponent = React.memo(() => { ... });
MyComponent.displayName = 'MyComponent';


反应/无儿童道具

React 组件接受一个名为children. 该道具的值将是元素的开始和结束标记内的任何内容。考虑这个简单的MyList组件:


const MyList = ({ children }) => {
  return <ul>{children}</ul>;
};
 
这将渲染外部ul元素,我们放置在元素内的任何子元素都将在其中渲染。
 
<MyList>
  <li>item1</li>
  <li>item2</li>
</MyList>
 
这是 React 组件的首选模式。尽管不推荐,但可以将 children 作为显式的 children 属性传递:
 
<MyList children={<li>item1</li><li>item2</li>} />
 
上述用法实际上会导致错误,因为 JSX 表达式,就像作为显式 children 属性传递的表达式一样,必须有一个根元素。这需要将孩子包裹在一个片段中:
 
<MyList children={<><li>item1</li><li>item2</li></>} />


如第一个示例所示,children 作为子元素直接传递给组件,因此组件是表达式的根元素。这里不需要片段或其他封闭元素。

这主要是一种风格选择/模式,但它确实可以防止无意中传递显式childrenprop 和子元素:


<MyList children={<><li>item1</li><li>item2</li></>}>
  <li>item3</li>
  <li>item4</li>
</MyList>


在这种情况下,将显示子元素 (item3和) ,item4但不会显示。此规则确保子元素仅以惯用方式作为子 JSX 元素传递。item1``item2

对孩子做出反应/没有危险

React 的dangerouslySetInnerHTMLprop 允许将任意标记设置为innerHTML元素的属性。通常不建议这样做,因为它会使您的应用程序暴露于跨站点脚本 (XSS) 攻击。但是,如果您知道您可以信任输入并且用例需要它,那么这种方法可能变得必要。

prop 需要一个具有__html属性的对象,其值为原始 HTML 字符串。该字符串将设置为innerHTML.

children因为这会替换任何现有的子内容,所以将它与道具结合使用是没有意义的。事实上,如果你尝试这样做,React 会抛出一个错误。与仅在开发模式中出现的一些错误(如PropTypes验证错误)不同,此错误会使您的应用程序崩溃。

该规则执行相同的规则。如果dangerouslySetInnerHTML与孩子一起使用,lint 规则将失败。最好在 linting 或构建时捕获这些错误,而不是在应用程序部署后由用户报告!

反应/jsx-no-bind

每次渲染 React 组件时,都会付出性能代价。通常,某些模式或实践会导致组件不必要地重新渲染自身。这种行为的原因有很多,这条规则有助于防止其中之一。

当组件内部定义了任何函数时,它将在每次渲染时都是一个新的函数对象。这意味着无论何时重新渲染组件,都会认为 prop 发生了变化。即使使用React.memo,组件也会重新渲染。

如果子组件有任何useEffect将该函数作为依赖项的调用,这可能会导致效果再次运行,从而产生可能导致浏览器冻结的无限循环的可能性。

启用此规则后,任何作为道具传递的函数都将被标记。

有两种方法可以解决这个问题。如果该函数不依赖于组件内部的任何其他内容,则可以将其移到组件之外,它只是一个普通函数,始终是相同的内存引用。这样可以确保每次都将相同的函数传递给 prop。

对于函数确实以某种方式依赖于组件的情况,通常的解决方法是使用useCallbackHook 来记忆它。函数中引用的任何属性都必须包含在useCallback依赖数组中;有时这需要对值或函数进行多级记忆。

这增加了一些复杂性,但有助于减少额外的渲染并防止无限循环。

包起来

这里涵盖的规则只是eslint-plugin-react插件提供的一些规则。一些规则可能是固执己见或过分热心,但大多数还具有配置选项以使其不那么严格。

还有另一个以 JSX 和可访问性实践为中心的非常有用的 ESLint 插件:eslint-plugin-jsx-a11y. 此插件中的规则检查您的 JSX 标记,以确保遵循良好的 HTML 可访问性实践。

这些 React ESLint 插件有助于避免常见的陷阱,特别是如果你还是 React 的新手。您甚至可以编写自己的规则和插件来涵盖其他情况!