让我们说我有一个状态,该状态取决于其他状态(例如,当A更改时,我希望B更改)。
创建一个观察A并在useEffect钩内设置B的钩子是否合适?
效果会级联,以便当我单击按钮时,第一个效果将触发,导致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>
)
}
答案 0 :(得分:11)
▶1.我可以在useEffect挂钩中设置状态吗?
原则上,您可以在需要的地方自由设置状态-包括在useEffect
和even during rendering内部。只需通过适当地和/或有条件地设置钩子deps
来确保避免无限循环即可。
▶2.可以说我有一些状态取决于其他状态。创建一个观察A并将B设置在useEffect钩内的钩子是否合适?
您刚刚描述了useReducer
的 经典用例:
当您具有包含多个子值的复杂状态逻辑或当下一个状态取决于时,通常
useReducer
优于useState
前一个。 (React docs)设置状态变量取决于 另一个状态变量的当前值时,您可能希望尝试将两者都替换为{{ 1}}。 [...]当您writing
useReducer
时,是考虑使用减速器的好时机。 (Dan Abramov, Overreacted blog)
setSomething(something => ...)
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"));
▶3.效果是否会级联,以便当我单击按钮时,第一个效果将触发,导致b改变,导致第二个效果在下一次渲染之前触发?
<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>
始终在 之后运行。提交渲染并应用DOM更改。第一个效果会触发,更改useEffect
并导致重新渲染。渲染完成后,由于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"));
▶4.这样的代码结构在性能方面有不利之处吗?
是的。通过将<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>
的状态更改包装在b
的单独useEffect
中,浏览器将具有一个附加的布局/绘制阶段-用户可能会看到这些效果。如果您不想尝试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"));
答案 1 :(得分:6)
在setState
中使用useEffect
将创建一个无限循环,很可能是您不想造成的。
useEffect
在每次渲染之后调用,并且在其中使用setState
时,它将导致组件重新渲染,从而调用useEffect
,依此类推。
在useState
内使用useEffect
不会引起无限循环的唯一情况是,当您将空数组作为useEffect
的第二个参数传递给useEffect(() => {....}, [])
时,表示效果函数应该被调用一次:仅在第一次安装/渲染之后。
答案 2 :(得分:3)
效果始终在渲染阶段完成后执行,即使您将setState置于一个效果内,另一效果也将读取更新的状态并仅在渲染阶段之后对其执行操作。
已经说过,除非有b
可能由于changing a
以外的其他原因而改变的可能性,最好还是以相同的效果执行两个动作,在这种情况下,您也想执行相同的逻辑
答案 3 :(得分:1)
出于将来的目的,这可能也有帮助:
在useEffect中使用setState是可以的,您只需要像已经描述的那样注意而不创建循环即可。
但这不是唯一可能发生的问题。见下文:
想象一下,您有一个组件“ Comp”从父级接收道具,并且根据道具更改,您希望设置“ Comp”状态。由于某些原因,您需要为每个道具以不同的useEffect进行更改:
请勿这样做
useEffect(() => {
setState({ ...state, a: props.a });
}, [props.a]);
useEffect(() => {
setState({ ...state, b: props.b });
}, [props.b]);
它可能永远不会更改a的状态,如您在本示例中所看到的: https://codesandbox.io/s/confident-lederberg-dtx7w
在此示例中发生这种情况的原因是,当同时更改prop.a和prop.b时,两个useEffects都在同一反应周期中运行,因此“ {... state}的值”,则在 setState 中,它们在 useEffect 中是完全相同的,因为它们是在相同的上下文中。当您运行第二个“ setState”时,它将替换第一个“ setState”。
请这样做
此问题的解决方案基本上是这样调用“ setState”:
useEffect(() => {
setState(state => ({ ...state, a: props.a }));
}, [props.a]);
useEffect(() => {
setState(state => ({ ...state, b: props.b }));
}, [props.b]);
在此处检查解决方案:https://codesandbox.io/s/mutable-surf-nynlx
现在,当继续进行setState时,您始终会收到状态的最新和正确值。
我希望这对某人有帮助!
答案 4 :(得分:0)
useEffect可以挂钩到某个道具或状态。因此,您需要做的是避免无限循环挂钩的事情是将某些变量或状态绑定到有效
例如
useEffect(myeffect, [])
只有在渲染组件后,上方效果才会触发。这类似于componentDidMount生命周期
const [something, setSomething] = withState(0)
const [myState, setMyState] = withState(0)
useEffect(() => {
setSomething(0)
}, myState)
以上效果将仅触发我已更改的状态,这与componentDidUpdate相似,不同之处在于并非所有更改的状态都将触发其
您可以通过此link
阅读更多详细信息