对于使用
useState钩子的功能组件,这是常见的问题。相同的考虑适用于
useState使用状态的任何回调函数,例如
setTimeout或
setInterval定时器函数。
事件处理程序在
CardsProvider和
Card组件中被区别对待。
handleCardClick并且
handleButtonClick在
CardsProvider功能组件中使用的组件在其范围内定义。每次运行时都有新功能,它们引用
cards在定义它们时获得的状态。每次
CardsProvider呈现组件时都会重新注册事件处理程序。
handleCardClick用于
Card功能组件的组件会作为道具接收并在组件支架上一次注册
useEffect。它在整个组件寿命期间都具有相同的功能,并且是指在首次
handleCardClick定义功能时新鲜的陈旧状态。
handleButtonClick作为道具接收并在每个
Card渲染器上重新注册,每次都是新功能,并引用新鲜状态。
可变状态
解决此问题的常用方法是使用
useRef而不是
useState。引用基本上是一种配方,提供了一个可变对象,可以通过引用传递该对象:
const ref = useRef(0);function eventListener() { ref.current++;}万一组件应该在状态更新时重新渲染,如预期的那样
useState,则refs不适用。
可以分别保持状态更新和可变状态,但是
forceUpdate在类和函数组件中都被视为反模式(列出仅供参考):
const useForceUpdate = () => { const [, setState] = useState(); return () => setState({});}const ref = useRef(0);const forceUpdate = useForceUpdate();function eventListener() { ref.current++; forceUpdate();}状态更新器功能
一种解决方案是使用状态更新程序功能,该功能从封闭的范围接收新鲜状态而不是陈旧状态:
function eventListener() { // doesn't matter how often the listener is registered setState(freshState => freshState + 1);}如果需要一个状态来实现同步副作用,例如
console.log,一种解决方法是返回相同状态以防止更新。
function eventListener() { setState(freshState => { console.log(freshState); return freshState; });}useEffect(() => { // register eventListener once}, []);这不适用于异步副作用,尤其是
async函数。
手动事件侦听器重新注册
另一种解决方案是每次都重新注册事件侦听器,因此回调总是从封闭范围获得新状态:
function eventListener() { console.log(state);}useEffect(() => { // register eventListener on each state update}, [state]);内置事件处理
除非在上注册了事件侦听器
document,
window或者其他事件目标不在当前组件的范围之内,否则在可能的情况下必须使用React自己的DOM事件处理,这样就不需要
useEffect:
<button onClick={eventListener} />在最后一种情况下,事件侦听器可以作为道具传递时,还可以通过
useMemo或
useCallback来记住,以防止不必要的重新渲染:
const eventListener = useCallback(() => { console.log(state);}, [state]);答案的先前版本建议使用可变状态,该可变状态适用
useState于React16.7.0-alpha版本中的初始钩子实现,但不适用于最终的React16.8实现。
useState当前仅支持不可变状态。



