reactjs - react js,如何顺序/同步更新相互依赖的状态?
问题描述
我有一个状态,我们称之为 B,当我改变状态 A 时我总是重置它。
但是:现在我想重置它,然后重新设置它。如何用 React js 做到这一点?
示例在这里: https ://codepen.io/jossnaz/pen/bGVXxev
目标是在输出中有“B is 1”
您不能只从 A 的 useEffect 中删除 setB(0)
为什么这不起作用?如何使它工作,正确的方式?
Javascript:
const { useState, useEffect } = React
const App = () => {
const [A, setA] = useState(0);
const [B, setB] = useState(0);
const [msgB, setMsgB] = useState('');
useEffect(()=>{
setB(0);
}, [A]);
useEffect(()=>{
setMsgB('B is: ' + B);
}, [B]);
const onClick = () =>{
setA(A ? 0 : 1); //basically a toggle
setB(1);
};
return(
<div className="box">
<button onClick={onClick}>click me to test </button>
<div><strong>A:</strong> {A}</div>
<div><strong>B:</strong> {B}</div>
<div>debug:</div>
<div>{msgB}</div>
</div>
);
}
ReactDOM.render(<App />,
document.getElementById("root"))
再说一次,我想要什么?
我想打电话
setA(A ? 0 : 1); //basically a toggle
setB(1); // <--------- make this work!
并且 B 的值为 1。现在它始终为 0。
编辑
最初的想法也是,这两个状态以非平凡的方式相互依赖。
例如,假设 setB(1) 依赖于 A。想象一下:也许setB(A == 1 ? 'one' : 'zero');
这是一个平凡的例子,但想象一个不平凡的例子
解决方案
您可以使用setTimeout
延迟为的 a 与处理程序0
分开更新 B。onClick
这将在处理事件队列时将该更新排入不同的渲染周期。当前渲染周期中的所有状态更新都将在事件队列末尾的超时回调中排队更新之前处理。
基本上是因为 React 状态更新是异步的,而是按照它们排队的顺序进行处理,再加上一个函数需要在处理效果之前完成的事实。在您的代码中,您将“更新(A,B1)”排队,但是当A更新时,它会排队“更新(B2)”并覆盖B1。当您使用setTimeout
它时,它将“update(B1)”放在事件队列的末尾,这将在渲染阶段的所有效果提交后处理。
const App = () => {
const [A, setA] = useState(0);
const [B, setB] = useState(0);
const [msgB, setMsgB] = useState("");
useEffect(() => {
setB(0);
}, [A]);
useEffect(() => {
setMsgB("B is: " + B);
}, [B]);
const onClick = () => {
setA(A ? 0 : 1); //basically a toggle
// setB(1) // <-- This update is overwritten by the setB(0) from effect A
setTimeout(setB, 0, 1); // <-- This delays a setB(1) after all effects processed
};
return (
<div className="box">
<button onClick={onClick}>click me to test </button>
<div>
<strong>A:</strong> {A}
</div>
<div>
<strong>B:</strong> {B}
</div>
<div>debug:</div>
<div>{msgB}</div>
</div>
);
};
您可以将延迟( 的第二个值setTimeout
)设置为 500 或 1000 之类的值,您会看到渲染B
值“blip”1,然后设置回 0,最后设置为 1。
编辑:在此处移动减速器解决方案以提高对话清晰度
挂钩每个渲染周期运行一次。效果是更新 A -> 重置 B。如果您还想要另一个效果,更新 B -> 重置 C,它必须等到下一个渲染周期才能获取 B 中的更改才能触发效果。像这样的每个效果都会将实际更改延迟1 个渲染周期。这没关系,直到您尝试通过在同一周期中将集合 B 排队并期望所有后来触发的效果都无法按设计工作来重置状态。
const initialState = {
A: 0,
B: 0,
C: 0
};
const reducer = (state, action) => {
switch (action.type) {
case "SET_A":
// Reset to { A: value, B: 0, C: 0 }
return { ...initialState, A: action.value };
case "SET_B":
// Keep A, set B, reset C
return { ...state, B: action.value, C: 0 };
case "SET_C":
// Keep A & B, set C
return { ...state, C: action.value };
default:
return state;
}
};
const App = () => {
const [{ A, B, C }, dispatch] = useReducer(reducer, initialState);
const [msgB, setMsgB] = useState("");
useEffect(() => {
setMsgB("B is: " + B);
}, [B]);
const onClickA = () => {
dispatch({ type: "SET_A", value: A ? 0 : 1 });
dispatch({ type: "SET_B", value: 1 });
dispatch({ type: "SET_C", value: 3 });
};
return (
<div className="box">
<div>
<strong>A:</strong> {A}
<button onClick={onClickA}>Toggle A</button>
</div>
<div>
<strong>B:</strong> {B}
</div>
<div>
<strong>C:</strong> {C}
</div>
<div>debug:</div>
<div>{msgB}</div>
</div>
);
};
如何/为什么这样做
由于对状态更新的“反应”造成的延迟,很难将异步更新调和为同步。超时在短期内有效,但即使只是添加第三个变量来使用它也会使它变得复杂。
然而,使用 reducer,我们可以控制需要同时重置的整个变量“集”( A、B、C、... Z? ) 。这意味着我们可以“折叠”所有我们知道/想要的状态值重置将发生在单个原子“动作”中,并在单个渲染周期中进行全部更新。使用 reducer 函数的好处是它序列化状态更新,这意味着它一次处理一个动作;在处理下一个分派的操作之前,它会运行到完成。
dispatch({ type: "SET_A", value: A ? 0 : 1 });
// We know here that state will be { A: <0|1>, B: 0, C: 0 }
dispatch({ type: "SET_B", value: 1 });
// We know here that state will be { A: <0|1>, B: 1, C: 0 }
dispatch({ type: "SET_C", value: 3 });
// We know here that state will be { A: <0|1>, B: 1, C: 3 }
推荐阅读
- matlab - 在 MATLAB 中更改通过 FIT 函数生成的表面网格
- c - C程序启动定义不清楚
- kotlin - 无法解析 TLS 数据包头 android studio
- allennlp - 在每个 epoch 后保存模型 - AllenNLP
- sql - BigQuery 子查询返回上个月的分组字段
- python - SyntaxError: 文件中的非 ASCII 字符 '\xe2' 但未声明编码
- webpack - 在 image-minimizer-webpack-plugin (mozjpeg, pngquant) 中使用有损模块时出错
- reactjs - 当我使用表单发送输入时,我无法更改状态
- angular - 使用 httpclient 时查看变量未从错误处理程序代码更新
- python - cv2.findContours() 和 cv2.drawContours() 未显示预期结果