反应 useContext 丢失状态
React useContext losing state
过去几天我一直在努力解决一个问题:
我正在为表单字段使用上下文提供程序,但无论出于何种原因,当我使用备忘录时,字段总是相互覆盖。
提供商:
export const Context = React.createContext();
function Form_Provider({ values, onChange, children }) {
return (
<Context.Provider value={{ values, onChange }}>
{children}
</Context.Provider>
)
});
export default Form_Provider
字段:
function Field({ label, name }) {
const { values, onChange } = useContext(Context);
return (
<Memorable
label={label}
onChange={({ target: t }) => onChange({ name, value: t.value })}
value={values[name]}
/>
);
}
const Memorable = React.memo(props => {
return (
<Form.Item label={props.label}>
<Input
value={props.value}
onChange={props.onChange}
/>
</Form.Item>
</>
)
}, ({ value: newValue}, { value: oldValue }) => newValue == oldValue)
表格
const [formValues, setFormValues] = useState({ field1: 'Foo', field2: 'Bar' });
<Form.Provider
values={formValues}
onChange={({ name, value }) => setFormValues({...formValues, [name]: value }))
>
<Form.Field name='field1' label="Field 1" />
<Form.Field name='field2' label="Field 2" />
</Form.Provider>
(尽量简化)
在我的实际代码中,我添加了一个 json 打印美化器来跟踪每个字段的状态,当我只编辑一个字段时,它会一直运行到每个字段。但是,一旦我开始编辑另一个字段,我编辑的第一个字段就会恢复到其原始状态 and/or 过去状态之间的其他一些奇怪的状态。
如果我不使用备忘录,它可以工作,但这不是解决方案,因为我将处理很多字段,这会导致大量重新渲染。
有人知道这里发生了什么吗?
加法:
我已经尝试为此使用一个内部减速器并传递一个调度函数。只要我不尝试在提供者之外管理状态,一切正常。
我很确定问题是您正在从这里记住 formValues
的原始值:
onChange={({ name, value }) => setFormValues({...formValues, [name]: value }))
假设 field1
的值发生了变化。它调用 onChange
处理程序,它将 ...formValues
- 组件安装时存在的值 - 与 field1
.
的新值合并
现在 React.memo
中的相等函数为 field1
returns false,因为值不同。该特定字段重新呈现以接收其新值,以及 ...formValues
的新值。然而,其他字段 not 重新呈现。对于他们来说,...formValues
仍然意味着状态的值,因为它存在于他们最后一次重新渲染时,也就是组件安装时。
如果您现在更改 field2
的值,它会将状态设置为将原始状态与 field2
的新值合并的结果。因此 field1
被重置,因为它的值现在又变回原来的值。
一个简单的解决方案是使用 setState
的回调版本,它始终使用状态的当前值:
onChange={({ name, value }) => setFormValues(fv => {...fv, [name]: value }))
但是,我很想不这样做,而是完全摆脱记忆。这是因为您的相等函数实际上并没有准确反映提供给组件的 props 变化的方式。我相信这里的性能提升也可以忽略不计,因为组件很小并且本身不会渲染任何其他组件。
假设没有与值变化相关的动画,它的执行成本非常低,并且不适合记忆,这也逃脱了内置的 React 优化。在实施之前,您应该仔细考虑决定您是否真的需要它。
过去几天我一直在努力解决一个问题:
我正在为表单字段使用上下文提供程序,但无论出于何种原因,当我使用备忘录时,字段总是相互覆盖。
提供商:
export const Context = React.createContext();
function Form_Provider({ values, onChange, children }) {
return (
<Context.Provider value={{ values, onChange }}>
{children}
</Context.Provider>
)
});
export default Form_Provider
字段:
function Field({ label, name }) {
const { values, onChange } = useContext(Context);
return (
<Memorable
label={label}
onChange={({ target: t }) => onChange({ name, value: t.value })}
value={values[name]}
/>
);
}
const Memorable = React.memo(props => {
return (
<Form.Item label={props.label}>
<Input
value={props.value}
onChange={props.onChange}
/>
</Form.Item>
</>
)
}, ({ value: newValue}, { value: oldValue }) => newValue == oldValue)
表格
const [formValues, setFormValues] = useState({ field1: 'Foo', field2: 'Bar' });
<Form.Provider
values={formValues}
onChange={({ name, value }) => setFormValues({...formValues, [name]: value }))
>
<Form.Field name='field1' label="Field 1" />
<Form.Field name='field2' label="Field 2" />
</Form.Provider>
(尽量简化)
在我的实际代码中,我添加了一个 json 打印美化器来跟踪每个字段的状态,当我只编辑一个字段时,它会一直运行到每个字段。但是,一旦我开始编辑另一个字段,我编辑的第一个字段就会恢复到其原始状态 and/or 过去状态之间的其他一些奇怪的状态。
如果我不使用备忘录,它可以工作,但这不是解决方案,因为我将处理很多字段,这会导致大量重新渲染。
有人知道这里发生了什么吗?
加法:
我已经尝试为此使用一个内部减速器并传递一个调度函数。只要我不尝试在提供者之外管理状态,一切正常。
我很确定问题是您正在从这里记住 formValues
的原始值:
onChange={({ name, value }) => setFormValues({...formValues, [name]: value }))
假设 field1
的值发生了变化。它调用 onChange
处理程序,它将 ...formValues
- 组件安装时存在的值 - 与 field1
.
现在 React.memo
中的相等函数为 field1
returns false,因为值不同。该特定字段重新呈现以接收其新值,以及 ...formValues
的新值。然而,其他字段 not 重新呈现。对于他们来说,...formValues
仍然意味着状态的值,因为它存在于他们最后一次重新渲染时,也就是组件安装时。
如果您现在更改 field2
的值,它会将状态设置为将原始状态与 field2
的新值合并的结果。因此 field1
被重置,因为它的值现在又变回原来的值。
一个简单的解决方案是使用 setState
的回调版本,它始终使用状态的当前值:
onChange={({ name, value }) => setFormValues(fv => {...fv, [name]: value }))
但是,我很想不这样做,而是完全摆脱记忆。这是因为您的相等函数实际上并没有准确反映提供给组件的 props 变化的方式。我相信这里的性能提升也可以忽略不计,因为组件很小并且本身不会渲染任何其他组件。
假设没有与值变化相关的动画,它的执行成本非常低,并且不适合记忆,这也逃脱了内置的 React 优化。在实施之前,您应该仔细考虑决定您是否真的需要它。