对于使用
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,则引用不适用。
可以分别保持状态更新和可变状态,但是
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 return () => { // unregister eventListener once };}, []);
这不适用于异步副作用,尤其是
async函数。手动事件侦听器重新注册
另一种解决方案是每次都重新注册事件侦听器,因此回调总是从封闭范围获得新状态:
内置事件处理function eventListener() { console.log(state);}useEffect(() => { // register eventListener on each state update return () => { // unregister eventListener };}, [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
当前仅支持不可变状态。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)