为什么映射 React 钩子有效,但 lambda 违反了钩子规则?
Why does mapping a React hook work, but a lambda violates rule of hooks?
如果我有一个钩子,例如:
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"
cannot be called inside a callback.
React Hooks must be called in a React function component or a custom React Hook
function.
(react-hooks/rules-of-hooks) eslint
为什么会有这种差异,它们不应该是等价的吗?
此外,如果这违反了钩子规则,应该如何处理?
让我们分析两种情况:
const labels = ['one', 'two'].map(useGetSet);
在这种情况下,useGetSet
在钩子内部被调用称为“内部自定义 React Hook 函数”。
const labels = ['one', 'two'].map((l) => useGetSet(l))
在这种情况下,useGetSet
是在匿名函数中调用的,因此“钩子规则”被打破了。
所以基本上:
第一种情况:Hook > hook的调用
第二种情况:Hook > 匿名函数 > hook的调用
与上一题相关:
Why this difference, shouldn't they be equivalent?
不,它们不等价。
在第一种情况下,回调函数引用是您命名的函数,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?
在此示例中,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));
这里有 2 个选项:
- 如果您需要在回调中调用挂钩但挂钩未呈现任何内容,那么您需要创建一个新挂钩,例如 https://codesandbox.io/s/sad-cache-zti5f?file=/src/TestComponent.jsx .
- 如果 lambda 在最后渲染 smth 那么你可以创建一个新组件
例如 https://codesandbox.io/s/quizzical-currying-pe15r?file=/src/TestComponent.js.
这两种情况都不好,但 ESLint 没有接受第一种情况。
您可以通过在 运行 时更改标签数组来查看这一点。如果你增加或减少长度,它就会爆炸(因为根据异常调用了意外数量的钩子),如果你不这样做,它就不会按预期重新渲染。 See here 获取演示的更新版本。
(规则存在的原因是因为在引擎盖下,每次都需要以相同的顺序调用挂钩。显然,如果渲染之间的挂钩数量不同,那么顺序就会改变。)
在您的特定情况下,因为您的数组是固定长度且不会更改,所以您的代码将正常工作(无论 linter 说什么)。但是,它仍然是一种危险的模式,最好避免。
就如何更好地处理它而言,如果您需要的不仅仅是基本的状态管理,那么像 useReducer
这样的方法就可以了。您可以根据需要使用任意标签,并定义 setter/getter 将标签作为参数的操作。
如果我有一个钩子,例如:
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"
cannot be called inside a callback.
React Hooks must be called in a React function component or a custom React Hook function. (react-hooks/rules-of-hooks) eslint
为什么会有这种差异,它们不应该是等价的吗?
此外,如果这违反了钩子规则,应该如何处理?
让我们分析两种情况:
const labels = ['one', 'two'].map(useGetSet);
在这种情况下,useGetSet
在钩子内部被调用称为“内部自定义 React Hook 函数”。
const labels = ['one', 'two'].map((l) => useGetSet(l))
在这种情况下,useGetSet
是在匿名函数中调用的,因此“钩子规则”被打破了。
所以基本上:
第一种情况:Hook > hook的调用
第二种情况:Hook > 匿名函数 > hook的调用
与上一题相关:
Why this difference, shouldn't they be equivalent?
不,它们不等价。
在第一种情况下,回调函数引用是您命名的函数,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?
在此示例中,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));
这里有 2 个选项:
- 如果您需要在回调中调用挂钩但挂钩未呈现任何内容,那么您需要创建一个新挂钩,例如 https://codesandbox.io/s/sad-cache-zti5f?file=/src/TestComponent.jsx .
- 如果 lambda 在最后渲染 smth 那么你可以创建一个新组件 例如 https://codesandbox.io/s/quizzical-currying-pe15r?file=/src/TestComponent.js.
这两种情况都不好,但 ESLint 没有接受第一种情况。
您可以通过在 运行 时更改标签数组来查看这一点。如果你增加或减少长度,它就会爆炸(因为根据异常调用了意外数量的钩子),如果你不这样做,它就不会按预期重新渲染。 See here 获取演示的更新版本。
(规则存在的原因是因为在引擎盖下,每次都需要以相同的顺序调用挂钩。显然,如果渲染之间的挂钩数量不同,那么顺序就会改变。)
在您的特定情况下,因为您的数组是固定长度且不会更改,所以您的代码将正常工作(无论 linter 说什么)。但是,它仍然是一种危险的模式,最好避免。
就如何更好地处理它而言,如果您需要的不仅仅是基本的状态管理,那么像 useReducer
这样的方法就可以了。您可以根据需要使用任意标签,并定义 setter/getter 将标签作为参数的操作。