【问题标题】:Why does mapping a React hook work, but a lambda violates rule of hooks?为什么映射 React 钩子有效,但 lambda 违反钩子规则?
【发布时间】:2021-05-14 11:54:29
【问题描述】:

如果我有一个钩子,例如:

const useGetSet = (label: string) => {
  const [get, set] = useState(label);
  return { get, set };
};

我可以通过数组map 它,例如:

const labels = ['one', 'two'].map(useGetSet);

但如果我将其扩展为 lambda,例如:

const labels = ['one', 'two'].map((l) => useGetSet(l))

然后它导致:

无法在回调中调用 React Hook "useGetSet"
必须在 React 函数组件或自定义 React Hook 中调用 React Hooks 功能。 (react-hooks/rules-of-hooks) eslint


为什么会有这种差异,它们不应该是等价的吗?

另外,如果这违反了钩子规则,应该如何这样做?

Full working example here.

【问题讨论】:

  • 我认为 ESLint 对它可以轻松识别为罚款的内容和被视为违规的内容有一些限制。
  • 虽然循环可以与钩子一起使用(假设迭代计数是硬编码的),但它们通常是不合理的。见:Why can't React Hooks be called inside loops or nested function?
  • 我很惊讶第一个是允许的。您正在使用map 中的钩子玩危险的领土。只有在这种情况下才可以,因为['one', 'two'] 是一个固定数组。如果您正在映射一个可以更改长度的数组,那么您将遇到重大问题。
  • 我认为我对规则实施的局限性的假设是正确的,请参阅the source code
  • Re: 应该怎么做,你可能会有不同的意见,但我的是将整个数组传递给一个钩子useGetSetArray:codesandbox.io/s/map-react-hooks-forked-7ggzv?file=/src/App.tsx

标签: javascript reactjs react-hooks eslint


【解决方案1】:

这两种情况都很糟糕,但 ESLint 没有选择第一种情况。

您可以通过在运行时更改标签数组来看到这一点。如果您增加或减少它爆炸的长度(因为根据例外情况调用了意外数量的钩子),如果您不这样做,它不会按预期重新渲染。 See here 获取演示的更新版本。

(规则存在的原因是因为在幕后,每次都需要以相同的顺序调用钩子。显然,如果钩子的数量在渲染之间发生变化,那么顺序就会改变。)

在您的特定情况下,因为您的数组是固定长度并且不会更改,所以您的代码将正常工作(无论 linter 说什么)。但是,这仍然是一种危险的模式,最好避免。

就如何更好地处理它而言,如果您需要的不仅仅是基本的状态管理,像useReducer 这样的东西会起作用。您可以拥有任意数量的标签,并定义将标签作为参数的 setter/getter 操作。

【讨论】:

【解决方案2】:

让我们分析两种情况:

const labels = ['one', 'two'].map(useGetSet);

在这种情况下,useGetSet 在钩子内部被称为“内部自定义 React Hook 函数”。

const labels = ['one', 'two'].map((l) => useGetSet(l))

在这种情况下,useGetSet 在匿名函数内部被调用,因此“钩子规则”被打破。

所以基本上:

在第一个场景中:钩子>钩子的调用

第二种情况:钩子>匿名函数>钩子的调用

与上一个问题有关:

为什么会有这种差异,它们不应该是等价的吗?

不,它们不等价。

在第一种情况下,回调函数引用是您命名的函数useGetSet

在第二种情况下,回调函数引用是一个匿名定义的新函数。

关于为什么这条规则如此重要的有趣解释可以在文档中找到,特别是在本节中:

https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

但也要考虑到在某些情况下禁用 linter 规则是安全的,如下所述:

Why can't React Hooks be called inside loops or nested function?

【讨论】:

  • 不,这两种情况都违反了规则——linter 无法判断
  • 好吧,我同意你的观点:没有以正确的方式使用钩子。但我不同意你说的“这两种情况都违反规则”,让我试着解释一下。正如 MDN 中所解释的,map 函数“按顺序”调用回调。现在文档中描述的规则解释说“通过遵循此规则,您可以确保每次组件呈现时以相同的顺序调用 Hooks”。因此,最后,由于我们谈论的是常量,因此实际上以相同的顺序调用了钩子。如果您能编辑我的答案以提供最佳答案,我将不胜感激。
  • 据我们所知,我认为我们正在进行的谈话可以带来巨大的好处。
  • '以相同的顺序调用钩子'不是规则,它是允许 Hooks 工作的基本要求。规则是“在组件的根级别调用 Hook,而不是在循环或条件语句中”。该规则的存在是为了尽量减少因违反基本要求而导致的意外错误。
【解决方案3】:

在这个例子中 useGetSet 是回调。

const labels = ['one', 'two'].map(useGetSet);

在这个例子中,你在回调中调用了钩子,所以错误是有意义的。这个答案很好地涵盖了规则存在的原因:Why can't React Hooks be called inside loops or nested function?

const labels = ['one', 'two'].map((l) => useGetSet(l));

你有两个选择:

  1. 如果您需要在回调中调用钩子,但钩子没有呈现任何内容,那么您需要创建一个新的钩子,例如 https://codesandbox.io/s/sad-cache-zti5f?file=/src/TestComponent.jsx
  2. 如果 lambda 在最后呈现 smth,那么您可以创建一个新组件 例如https://codesandbox.io/s/quizzical-currying-pe15r?file=/src/TestComponent.js

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-06-10
    • 2019-04-02
    • 1970-01-01
    • 2020-12-02
    • 1970-01-01
    • 2021-01-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多