我发现自己处于一种奇怪的状况。我正在实现一个钩子,我无法设法实现自己想要的目标。
我有这样的东西:
const appHook = props => {
const [foo, setFoo] = React.useState([]);
const [bar, setBar] = React.useState([]);
React.useEffect(() => {
setFoo(getFoo(props.fooList, props.fooId));
setBar(getBar(foo.listToFilter));
}, [props.fooId]);
const getCurrentBlockTrade = (arrayToFilter, number) =>
arrayToFilter.filter(array => array.id === number);
const getSubOutList = (...) => {
...
};
return (<div>something</div>)
}
我的问题是setFoo函数已正确执行,因此foo状态是一个新数组,但是取决于foo状态的setBar会收到一个空数组。基本上setBar是在setFoo完成之前执行的,因此getBar函数会收到一个空数组。
管理这种依赖性的正确方法是什么?
谢谢, F。
答案 0 :(得分:3)
setState
是一个异步函数,这就是为什么您在setBar
函数中收到一个空数组的原因。基本上,您不确定在第二个setState
求值之前状态是否会更新。
在两种情况下为什么不简单提及道具?
React.useEffect(() => {
const newFoo = getFoo(props.fooList, props.fooId);
setFoo(newFoo);
setBar(getBar(newFoo.listToFilter));
}, [props.fooId]);
答案 1 :(得分:3)
TL; DR; 您的解决方案很可能是kind user
's answer
下面,我将描述我到目前为止在整个研究过程中所学到的思想,并通过博客等方式提出人们的 5个建议/解决方案。
您已经说过:
我的问题是setFoo函数已正确执行,因此foo状态是一个新数组,但是取决于foo状态的setBar会收到一个空数组。基本上 setBar在setFoo完成之前执行,因此getBar函数接收一个空数组。
你是真的。基本上是因为在React(Hooks和类组件)中,setState
是异步的。这是什么意思?这意味着setSomething只是告诉React重新渲染组件稍后。不能神奇地替换当前正在运行的函数中的const something
变量-这是不可能的。
const [foo, setFoo] = useState(0)
function handleClick() {
setFoo(42)
// we declared foo with const, you "obviously" shouldn't expect this
// to "somehow" immediately change `foo` to 42
console.log(foo);
// it's 0 in this render, BUT on next render, `foo` will be 42
}
最简单的技术是将foo
的新计算值存储在变量中,然后将新计算的值同时用于setFoo和setBar-这是一种非常流行的技术。
React.useEffect(() => {
const newFoo = getFoo(props.fooList, props.fooId);
setFoo(newFoo);
setBar(getBar(newFoo.listToFilter));
}, [props.fooId]);
// or: shouldn't use this, only to demonstrate the callback syntax in
// the new setState Hook (different than the old callback syntax setState):
React.useEffect(() => {
setFoo(() => {
const newFoo = getFoo(props.fooList, props.fooId);
setBar(getBar(newFoo.listToFilter));
return newFoo;
})
}, [props.fooId]);
可以在此处找到另一种技术:https://stackoverflow.com/a/54120692/9787887正在将useEffect
的依赖项列表用于setBar
到foo
。
React.useEffect(() => {
setFoo(getFoo(props.fooList, props.fooId));
}, [props.fooId]);
React.useEffect(() => {
setBar(getBar(foo.listToFilter));
}, [foo]);
尽管答案得到27票赞成,但我认为情况太复杂了,而且(据我所知)也应避免使该组件不必要地重新渲染2次而不是1次。
另一种可行的解决方案是使用async/await
来异步触发状态更改,以使更改不被批处理(关于此答案https://stackoverflow.com/a/53048903/9787887)
React.useEffect(async () => {
await setFoo(getFoo(props.fooList, props.fooId));
await setBar(getBar(foo.listToFilter));
}, [props.fooId]);
// no, actually this will not work!! it'll throw you an (annoyed) error
// the actual working code is:
React.useEffect(() =>
const setFooAndBar = async () => {
await setFoo(getFoo(props.fooList, props.fooId));
await setBar(getBar(foo.listToFilter));
}
setFooAndBar();
}, [props.fooId]);
您会看到,工作代码再次是另一个过于复杂(且不好)的解决方案,(但无论如何都要引入它??)。
gaearon mentioned的另一种解决方案是使用useReducer
- 使用Hooks,您还可以使用Reducer集中状态更新逻辑并避免这种陷阱。
他的另一个见解:
- 推荐的解决方案是使用一个变量而不是两个变量(因为似乎可以从另一个变量中计算出一个变量),或者先计算下一个值,然后一起使用它们更新它们。或者,如果您准备好进行跳跃,则useReducer可以帮助避免这些陷阱。
但是对于这种情况,这似乎又是另一个过于复杂的建议,不是吗?
最后的建议是a comment of gaearon,告诉您重新考虑您对状态的依赖,真的需要状态依赖吗?
最好的解决方案是不具有从其他状态计算得出的状态。 如果始终根据
this.state.y
计算this.state.x
,请完全删除this.state.y
,仅跟踪this.state.x
。并计算渲染时所需的内容>
感谢您有足够的耐心阅读这里:))。
答案 2 :(得分:2)
设置状态是一个异步过程。因此setBar(getBar(foo.listToFilter));
调用此foo是空数组。您可以为此使用另一个useEffect
React.useEffect(() => {
setFoo(getFoo(props.fooList, props.fooId));
}, [props.fooId]);
React.useEffect(() => {
setBar(getBar(foo.listToFilter));
}, [foo]);