[英]React - What's the best way of passing a state setter function to a helper function from a component?
[英]If a function updates a state variable which is an object and is called from a child component, what's the best way to avoid infinite renders?
我在 React 上做一些编码,遇到了一个我想妥善处理的问题。 下文提供了有关此事的详细信息。
假设您有一个组件FormDemo
来处理可能很复杂的表单,其中部分涉及某些输入字段的动态管理。 例如,由于 JavaScript 对数组长度的限制,所提供的代码示例允许为 0 和 (2 32 - 1) 之间的字段创建任意数量的字段。
按所有名称字段上方的添加新名称按钮到 append 另一个名称字段。 按任何输入右侧的删除按钮将其从列表中删除。
创建的每个名称输入都由一个单独的组件SubForm
处理,该组件具有三个属性:
这是我在 CodeSandbox 上制作的代码的工作示例,用于演示目的。
代码示例中使用的方法有效,但它有 CodeSandbox 的 Problems 选项卡中提到的 eslint 问题,我知道这不是 CodeSandbox 问题,因为我在本地环境中测试了同一个项目并遇到了同样的问题. 以下是直接从控制台获取的问题详细信息:
React Hook useEffect 缺少依赖项:'onChange'。 包括它或删除依赖数组。 如果 'onChange' 更改过于频繁,请找到定义它的父组件并将该定义包装在 useCallback 中。 (react-hooks/exhaustive-deps)
直接遵循问题的建议(即添加onChange
到SubForm
的 useEffect 的依赖项列表)会导致无限渲染,因此不是问题的解决方案。
在阅读了一些关于 useCallback 的官方 React文档以及关于 useEffect的其他部分之后,我发现在渲染组件时,React 会创建在组件主体中声明的函数的新实例。 因此,将此类函数添加到某个useEffect
挂钩的依赖项列表中,该挂钩具有附加到它的效果 function 将需要在每个渲染上调用 function。
在我的方法中,我将update
function 传递给onChange
属性中的SubForm
组件作为参考( 此处由 React 文档证明),因此SubForm
组件的onChange
属性具有与父组件完全相同的update
function 实例。 因此,每当update
function 的实例发生变化时,它添加到useEffect
挂钩的依赖项中,该挂钩执行附加到它的效果 function ,并且考虑到上述情况,这发生在父组件FormDemo
的每次渲染上。
update
function 更改了FormDemo
组件的 state 变量forms
的值,导致它重新呈现。 这将重新创建update
function 的实例。 SubForm
组件收到该更改的通知并执行附加到useEffect
挂钩的效果 function,再次调用update
function。 反过来,这会导致 state 变量forms
的另一个变化,告诉父组件FormDemo
再次渲染......这会无限期地继续,创建一个无限循环的渲染。
有些人可能会问,如果表单的输入字段在这两个渲染之间没有更改,为什么会发生这种情况,因此传递给update
function 的值实际上与以前相同。 经过一些测试,你可以 在这里自己尝试,我得出的结论是它实际上是错误的:设置为forms
的值总是不同的。 这是因为即使对象的内容完全相同,但它的实例是不同的,React 比较的是 object 实例而不是它们的内容,如果这些实例不同,则发送一个重新渲染组件的命令。
As useCallback
hook memoizes the instance of the function between renders, recreating the function only when the values or instances of its dependencies change, I've assumed that wrapping update
function in that hook will solve the original problem, because the instance of the function will始终保持不变。
但是,在 useCallback 中包装update
useCallback
会导致另一个问题:我需要添加forms
作为依赖项,因为我在 ZC1C425268E68385D1AB5074C17A94F14 中使用它。 但是,考虑到上述情况,由于forms
的实例在每次更新后都不同,这将使我回到原来的问题,这将命令useCallback
重新创建 function 的实例。
尽管如此,我有一个我不太喜欢的解决方案,即使它有效,因为它不需要将 state 变量forms
添加到useCallback
的依赖项列表中:
const update = useCallback((id, value) => {
setForms(prevState => {
const { form_list } = prevState,
new_forms = [...form_list],
mod_id = new_forms.map((e) => e.id).indexOf(id);
new_forms[mod_id] = value;
return { ...prevState, form_list: new_forms };
});
}, []);
那么我为什么反对它,如果它有效并且在控制台中没有问题呢?
以我的拙见(请随意证明我错了),因为这些问题:
在提供的将使用中间件 function setField
的情况下,对所述问题的最节省内存和可读性的解决方案是什么? 或者,如果可以用潜在的解决方案来揭穿我的问题,请证明这是 go 的最佳方法。
如果需要解决方案,请随意修改setField
的内容,并记住我愿意回答与问题相关的任何内容。
您可能希望将 ID 的管理与 SubFrom 分开。 SubForm 不应该改变它的 ID。
update
和remove
功能 - 所以 SubForm 不需要发回 ID。 <SubForm key={e.id} id={e.id}
onChange={(form) => update(e.id, form)}
onRemove={() => remove(e.id) } />
form
的一部分 const update = (id, value) => {
setField(
"form_list",
// subform shouldn't change id, so we overriding it (to be sure)
forms.form_list.map(e => e.id===id?{...value, id}:e)
);
};
您仍然可以选择将 ID 传递给 SubForm,但 ID 的管理与它是分开的。
看来您正在复制每个 SubForm 的SubForm
:您将其存储在 parent 和SubForm
中,为什么不将 state 仅存储在 parent 中并作为道具传递?
我在谈论这样的事情:
const SubForm = ({ id, form, onChange, onRemove }) => {
return (
<Form>
<Form.Group controlId={`form_text${id}`}>
<Form.Label>Name (ID {id})</Form.Label>
<InputGroup>
<Form.Control
type="text"
value={form.name}
onChange={(e) => onChange(id, { ...form, name: e.target.value })}
/>
<Button variant="danger" onClick={() => onRemove(id)}>
Remove
</Button>
</InputGroup>
</Form.Group>
<br />
<br />
</Form>
);
};
要传递每个表单数据,只需执行以下操作:
<SubForm key={e.id} form={e} id={e.id} onChange={update} onRemove={remove} />
不再需要useEffect
了。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.