假设我有一些依赖于其他状态的状态(例如,当 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>
)
}
一般来说,在 useEffect
中使用 setState
会创建一个您很可能不想导致的无限循环。该规则有几个例外,我稍后会介绍。
useEffect
在每次渲染后调用,当在其中使用 setState
时,它将导致组件重新渲染,这将调用 useEffect
等等。
在 useEffect
中使用 useState
不会导致无限循环的流行情况之一是当您将空数组作为第二个参数传递给 useEffect
时,例如 useEffect(() => {....}, [])
,这意味着应该调用一次效果函数:仅在第一次安装/渲染之后。当您在组件中进行数据获取并且希望将请求数据保存在组件的状态中时,这会被广泛使用。
为了将来的目的,这也可能有所帮助:
可以在 useEffect
中使用 setState,您只需要注意已经描述的不要创建循环。
但这不是可能发生的唯一问题。见下文:
假设您有一个组件 Comp
,它从父级接收 props
,并根据 props
更改设置 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 在同一个反应周期中运行,所以当您更改 setState
时 {...state}
的值是两个 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
时,您始终会收到最新且正确的状态值。
我希望这可以帮助别人!
setName(name => ({ ...name, a: props.a }));
setItems(items => [...items, item])
部分也帮助了我
setState(state => ({ ...state, useEffectValue }));
当我登录 state
时,它仍然是空的。如果我在输出数组中传递 useEffectValue
,我会得到一个无限循环。 ¯_(ツ)_/¯
即使您在一个效果内设置状态,效果也总是在渲染阶段完成后执行,另一个效果将读取更新的状态并仅在渲染阶段后对其进行操作。
话虽如此,最好以相同的效果采取两种操作,除非 b
可能由于 changing a
以外的原因而改变,在这种情况下,您也希望执行相同的逻辑
useEffect
可以挂钩某个道具或状态。所以,为了避免无限循环钩子,你需要做的就是绑定一些变量或状态来生效
例如:
useEffect(myeffectCallback, [])
仅在组件渲染后才会触发上述效果。这类似于 componentDidMount
生命周期
const [something, setSomething] = withState(0)
const [myState, setMyState] = withState(0)
useEffect(() => {
setSomething(0)
}, myState)
上面的效果只会触发我的状态已经改变这类似于componentDidUpdate
,除了不是每个变化的状态都会触发它。
您可以通过此link阅读更多详细信息
withState()
代表什么。我在文档中找不到任何对它的引用。
▶ 1. 我可以在 useEffect 挂钩中设置状态吗?
原则上,您可以在需要的地方自由设置状态 - 包括在 useEffect
和 even during rendering 内。只需通过正确设置 Hook deps
和/或有条件地声明来确保避免无限循环。
▶ 2. 假设我有一些依赖于其他状态的状态。创建一个观察 A 并在 useEffect 钩子内设置 B 的钩子是否合适?
您刚刚描述了 useReducer
的 经典用例:
当您具有涉及多个子值的复杂状态逻辑或下一个状态取决于前一个状态时,useReducer 通常比 useState 更可取。 (React 文档)当设置状态变量取决于另一个状态变量的当前值时,您可能想尝试将它们都替换为 useReducer。 [...] 当您发现自己在编写 setSomething(something => ...) 时,现在是考虑改用 reducer 的好时机。 (丹·阿布拉莫夫,反应过度的博客)
let MyComponent = () => { let [state, dispatch] = useReducer(reducer, { a: 1, b: 2 }); useEffect(() => { console.log("B 的一些影响"); }, [state.b]); return (
A: {state.a}, B: {state.b}
▶ 3. 效果是否会级联,当我单击按钮时,第一个效果会触发,导致 b 发生变化,导致第二个效果在下一次渲染之前触发?
useEffect
始终在提交渲染并应用 DOM 更改之后运行。第一个效果触发,更改 b
并导致重新渲染。此渲染完成后,由于 b
更改,将运行第二个效果。
让 MyComponent = props => { console.log("render");让 [a, setA] = useState(1);让 [b, setB] = useState(2);让 isFirstRender = useRef(true); useEffect(() => { console.log("useEffect a, value:", a); if (isFirstRender.current) isFirstRender.current = false; else setB(3); return () => { console.log( "卸载 useEffect a, value:", a); }; }, [a]); useEffect(() => { console.log("useEffect b, value:", b); return () => { console.log("unmount useEffect b, value:", b); }; }, [b ]); return (
a: {a}, b: {b}
▶ 4. 像这样结构化代码是否有任何性能劣势?
是的。通过将 b
的状态更改包装在 a
的单独 useEffect
中,浏览器有一个额外的布局/绘制阶段 - 这些效果可能对用户可见。如果您无法尝试 useReducer
,您可以直接将 b
状态与 a
一起更改:
让 MyComponent = () => { console.log("render");让 [a, setA] = useState(1);让 [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!");集A(5); b >= 5 ?集合B(1):集合B(b + 1); }; return (
a: {a}, b: {b}
尝试将 setState 包装在一个 if 语句中,以检查状态是否需要更改 - 如果是,请更改它,否则 return () => {}
例如,
useEffect(() => {
if(a.currentCondition !== a.desiredCondition) {
setA();
}
return cleanup;
}, [b])
不定期副业成功案例分享
useEffect
中使用setState
的另一种情况是在订阅或事件侦听器中使用setting state
。但别忘了取消订阅reactjs.org/docs/hooks-effect.html#effects-with-cleanup