Javascript 我可以在 useEffect 钩子内设置状态吗
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/53715465/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Can I set state inside a useEffect hook
提问by Dan Ruswick
Lets say I have some state that is dependent on some other state (eg when A changes I want B to change).
假设我有一些依赖于其他状态的状态(例如,当 A 更改时,我希望 B 更改)。
Is it appropriate to create a hook that observes A and sets B inside the useEffect hook?
在 useEffect 钩子中创建一个观察 A 并设置 B 的钩子是否合适?
Will the effects cascade such that, when I click the button, the first effect will fire, causing b to change, causing the second effect to fire, before the next render? Are there any performance downsides to structuring code like this?
效果是否会级联,当我单击按钮时,第一个效果将触发,导致 b 更改,导致第二个效果在下一次渲染之前触发?像这样构造代码是否有任何性能缺点?
let MyComponent = props => {
let [a, setA] = useState(1)
let [b, setB] = useState(2)
useEffect(
() => {
if (/*some stuff is true*/) {
setB(3)
}
},
[a],
)
useEffect(
() => {
// do some stuff
},
[b],
)
return (
<button
onClick={() => {
setA(5)
}}
>
click me
</button>
)
}
采纳答案by Shubham Khatri
Effects are always executed after the render phase is completed even if you setState inside the one effect, another effect will read the updated state and take action on it only after the render phase.
效果总是在渲染阶段完成后执行,即使您在一个效果中设置了状态,另一个效果将读取更新的状态并仅在渲染阶段之后对其采取行动。
Having said that its probably better to take both actions in the same effect unless there is a possibility that bcan change due to reasons other than changing ain which case too you would want to execute the same logic
话虽如此,以相同的效果采取两种行动可能会更好,除非有可能b由于其他原因而改变,而不是changing a在这种情况下,您也希望执行相同的逻辑
回答by Hossam Mourad
Generally speaking, using setStateinside useEffectwill create an infinite loop that most likely you don't want to cause. There are a couple of exceptions to that rule which I will get into later.
一般来说,使用setStateinsideuseEffect会创建一个你很可能不想引起的无限循环。该规则有几个例外,我将在稍后介绍。
useEffectis called after each render and when setStateis used inside of it, it will cause the component to re-render which will call useEffectand so on and so on.
useEffect在每次渲染后调用,当setState在其中使用时,它将导致组件重新渲染,这将调用useEffect等等。
One of the popular cases that using useStateinside of useEffectwill not cause an infinite loop is when you pass an empty array as a second argument to useEffectlike useEffect(() => {....}, [])which means that the effect function should be called once: after the first mount/render only. This is used widely when you're doing data fetching in a component and you want to save the request data in the component's state.
使用useStateinside ofuseEffect不会导致无限循环的一种流行情况是,当您将空数组作为第二个参数传递给useEffectlike 时useEffect(() => {....}, []),这意味着应该调用一次效果函数:仅在第一次挂载/渲染之后。当您在组件中进行数据获取并且希望将请求数据保存在组件的状态中时,这被广泛使用。
回答by while true
For future purposes, this may help too:
为了将来的目的,这也可能有帮助:
It's ok to use setState in useEffect you just need to have attention as described already to not create a loop.
在 useEffect 中使用 setState 是可以的,您只需要注意已经描述过的不要创建循环。
But it's not the only problem that may occur. See below:
但这并不是可能发生的唯一问题。见下文:
Imagine that you have a component "Comp" that receives props from parent and according to a props change you want to set "Comp" state. For some reason, you need to change for each prop in a different useEffect:
想象一下,您有一个组件“Comp”,它从父级接收道具,并根据道具更改设置“Comp”状态。出于某种原因,您需要在不同的 useEffect 中为每个 prop 进行更改:
DO NOT DO THIS
不要这样做
useEffect(() => {
setState({ ...state, a: props.a });
}, [props.a]);
useEffect(() => {
setState({ ...state, b: props.b });
}, [props.b]);
It may never change the state of a as you can see in this example: https://codesandbox.io/s/confident-lederberg-dtx7w
正如您在此示例中看到的那样,它可能永远不会改变 a 的状态:https: //codesandbox.io/s/confident-lederberg-dtx7w
The reason why this happen in this example it's because both useEffects run in the same react cyclewhen you change both prop.a and prop.b so the value of "{...state}" when you do setStateare exactly the same in both useEffectbecause they are in the same context. When you run the second "setState" it will replace the first "setState".
在这个例子中发生这种情况的原因是因为当你同时改变 prop.a 和 prop.b 时,两个 useEffects 运行在同一个反应周期中,所以当你执行setState时 "{...state}" 的值在两者都是useEffect,因为它们处于相同的上下文中。当您运行第二个“setState”时,它将替换第一个“setState”。
DO THIS INSTEAD
改为这样做
The solution for this problem is basically call "setState" like this:
这个问题的解决方案基本上是像这样调用“setState”:
useEffect(() => {
setState(state => ({ ...state, a: props.a }));
}, [props.a]);
useEffect(() => {
setState(state => ({ ...state, b: props.b }));
}, [props.b]);
Check the solution here: https://codesandbox.io/s/mutable-surf-nynlx
在此处查看解决方案:https: //codesandbox.io/s/mutable-surf-nynlx
Now, you always receive the most updated and correct value of the state when you proceed with the setState.
现在,当您继续使用 setState 时,您总是会收到最新且正确的状态值。
I hope this helps someone!
我希望这可以帮助别人!
回答by Dogies007
useEffect can hook on a certain prop or state. so, the thing you need to do to avoid infinite loop hook is binding some variable or state to effect
useEffect 可以挂钩某个道具或状态。所以,你需要做的事情来避免无限循环钩子是绑定一些变量或状态来生效
eg.
例如。
useEffect(myeffect, [])
above effect will fire only once the component has rendered. this is similar to componentDidMount lifecycle
上述效果只会在组件渲染后触发。这类似于 componentDidMount 生命周期
const [something, setSomething] = withState(0)
const [myState, setMyState] = withState(0)
useEffect(() => {
setSomething(0)
}, myState)
above effect will fire only my state has changed this is similar to componentDidUpdate except not every changing state will fire its
上述效果只会触发我的状态发生变化这类似于 componentDidUpdate 除了不是每个变化的状态都会触发它的
you can read more detail though this link
您可以通过此链接阅读更多详细信息
回答by ford04
? 1. Can I set state inside a useEffect hook?
? 1. 我可以在 useEffect 钩子中设置状态吗?
In principle, you can set state freely where you need it - including inside useEffectand even during rendering. Just make sure to avoid infinite loops by settting Hook depsproperly and/or state conditionally.
原则上,你可以自由设定的状态,你需要它-包括内部useEffect和即使在渲染。只要确保通过deps正确设置 Hook和/或有条件地状态来避免无限循环。
? 2. Lets say I have some state that is dependent on some other state. Is it appropriate to create a hook that observes A and sets B inside the useEffect hook?
? 2. 假设我有一些状态依赖于其他状态。在 useEffect 钩子中创建一个观察 A 并设置 B 的钩子是否合适?
You just described theclassic use case for useReducer:
您刚刚描述了以下的经典用例useReducer:
useReduceris usually preferable touseStatewhen you have complex statelogic that involves multiple sub-valuesor when the next state depends on the previous one. (React docs)When setting a statevariable depends onthe current value of another statevariable, you might want to try replacing them both with
useReducer. [...] When you find yourself writingsetSomething(something => ...), it's a good time to consider using a reducer instead. (Dan Abramov, Overreacted blog)
useReduceruseState当您具有涉及多个子值的复杂状态逻辑或下一个状态取决于前一个状态时,通常更可取。(反应文档)当设置状态变量取决于另一个状态变量的当前值时,您可能想尝试将它们都替换为
useReducer. [...] 当您发现自己在编写 时setSomething(something => ...),现在是考虑使用 reducer 的好时机。(丹·阿布拉莫夫,反应过度的博客)
let MyComponent = () => {
let [state, dispatch] = useReducer(reducer, { a: 1, b: 2 });
useEffect(() => {
console.log("Some effect with B");
}, [state.b]);
return (
<div>
<p>A: {state.a}, B: {state.b}</p>
<button onClick={() => dispatch({ type: "SET_A", payload: 5 })}>
Set A to 5 and Check B
</button>
<button onClick={() => dispatch({ type: "INCREMENT_B" })}>
Increment B
</button>
</div>
);
};
// B depends on A. If B >= A, then reset B to 1.
function reducer(state, { type, payload }) {
const someCondition = state.b >= state.a;
if (type === "SET_A")
return someCondition ? { a: payload, b: 1 } : { ...state, a: payload };
else if (type === "INCREMENT_B") return { ...state, b: state.b + 1 };
return state;
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect } = React</script>
? 3. Will the effects cascade such that, when I click the button, the first effect will fire, causing b to change, causing the second effect to fire, before the next render?
? 3. 效果是否会级联,当我点击按钮时,第一个效果会触发,导致 b 改变,导致第二个效果在下一次渲染之前触发?
useEffectalways runs afterthe render is committed and DOM changes are applied. The first effect fires, changes band causes a re-render. After this render has completed, second effect will run due to bchanges.
useEffect始终在提交渲染并应用 DOM 更改后运行。第一个效果触发、改变b并导致重新渲染。此渲染完成后,第二个效果将由于b更改而运行。
let MyComponent = props => {
console.log("render");
let [a, setA] = useState(1);
let [b, setB] = useState(2);
let isFirstRender = useRef(true);
useEffect(() => {
console.log("useEffect a, value:", a);
if (isFirstRender.current) isFirstRender.current = false;
else setB(3);
return () => {
console.log("unmount useEffect a, value:", a);
};
}, [a]);
useEffect(() => {
console.log("useEffect b, value:", b);
return () => {
console.log("unmount useEffect b, value:", b);
};
}, [b]);
return (
<div>
<p>a: {a}, b: {b}</p>
<button
onClick={() => {
console.log("Clicked!");
setA(5);
}}
>
click me
</button>
</div>
);
};
ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
? 4. Are there any performance downsides to structuring code like this?
? 4. 像这样构造代码有什么性能上的缺点吗?
Yes. By wrapping the state change of bin a separate useEffectfor a, the browser has an additional layout/paint phase - these effects are potentially visible for the user. If there is no way you want give useReducera try, you could change bstate together with adirectly:
是的。通过将 的状态变化包装b在单独的useEffectfor 中a,浏览器有一个额外的布局/绘制阶段 - 这些效果对用户来说可能是可见的。如果没有办法useReducer尝试,可以直接使用以下命令更改b状态a:
let MyComponent = () => {
console.log("render");
let [a, setA] = useState(1);
let [b, setB] = useState(2);
useEffect(() => {
console.log("useEffect b, value:", b);
return () => {
console.log("unmount useEffect b, value:", b);
};
}, [b]);
const handleClick = () => {
console.log("Clicked!");
setA(5);
b >= 5 ? setB(1) : setB(b + 1);
};
return (
<div>
<p>
a: {a}, b: {b}
</p>
<button onClick={handleClick}>click me</button>
</div>
);
};
ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

