React监听事件执行的方法中如何获取最新的state

7,690 阅读3分钟

由于某种原因(这个原因我还说不清楚,你可以帮我完整的描述一次吗🤕),我们在react的某些时候调用的方法中无法获取到最新的state,比如事件监听

举个例子,在某个页面添加一个DOM监听点击事件,点击后输出count的值,这个count可能会通过其他方法改变

如果是class的写法,我们就没有这个疑问,因为在执行方法中我们都是直接从this.state里的值,始终是最新的;如果修改后需要立即用最新的值做其他的操作,也可以利用setState的回调函数

state = {
    count: 0,
};

componentDidMount() {
  // 添加事件监听
  document.addEventListener("click", () => handleClick(), true)
}
componentWillUnmount() {
  // 移除事件监听
  document.removeEventListener("click", () => handleClick(), true)
}

const handleClick = () => {
    this.setState({
    	count: this.state.count + 1  //设置一个新的值
    }, () => {
    	// 这里的state是最新的
    	console.log(this.state.count)
    })
}

当使用react Hooks时,常规写法是

const [count, setCount] = useState<number>(0);

useEffect(() => {
	// 添加事件监听
  	document.addEventListener("click", handleClick, true)
  return () => {
  	// 移除事件监听
  	document.removeEventListener("click", handleClick, true)
  }
}, [])

const handleClick = () => {
	// 执行一些读取state的操作
    console.log(count);
    setCount(count+1);
}

我们平时就很容易写成上面这样,这个时候就发现,count永远输出的是初始值0,然后页面渲染的count值除了第一次点击从0变成1后,就再也不会变化了真是个头痛的问题,也不报错,不报错的bug最难找咯🥺

解决办法:

  1. 由于state的值改变,我们可以在useEffect中监听这个state的改变,当state改变,则取消上一次的监听,并重新新的一轮监听,这样在监听执行方法中获取的state就一直都是最新的了
useEffect(() => {
	// 添加事件监听
  	document.addEventListener("click", handleClick, true)
  return () => {
  	// 移除事件监听
  	document.removeEventListener("click", handleClick, true)
  }
}, [count])

这是一种解决办法,但是每一次count的修改,都会取消上一次的监听,并重新绑定监听,如果多几个这样的操作,可能会消耗内存

  1. 通过 ref 来保存可变变量,声明一个useRef,每次state更新,都把新值赋给这个ref,在执行的方法中使用这个ref代替state
const [count, setCount] = useState<number>(0);
const countRef = useRef<number>(count);

useEffect(() => {
	// 添加事件监听
  	document.addEventListener("click", handleClick, true)
  return () => {
  	// 移除事件监听
  	document.removeEventListener("click", handleClick, true)
  }
}, [])

useEffect(() => {
	countRef.current = count;   //让countRef.current永远和count保持一致,避免其他操作改变了count的值
}, [count]) 

const handleClick = () => {
	// 使用countRef.current代替count
    console.log(count);
    setCount(countRef.current+1);
}

运行后会发现,在监听执行函数中打印的count始终还是初始值0,但是页面渲染的count已经随时在改变啦~

我们要知道在handleClick函数中拿到的count和函数外拿到的count已经不是同一个东西了,函数内的count在开始监听document.addEventListene的时候已经确定了。

这是我目前知道的两种方法,有更好的方法时更新~


看文档果然才是最重要的,文档明确说了setState除了可以接收state的最新值外,还可以接收一个函数,这个函数的参数是旧的state,返回值为新值,所以我们可以这样写

useEffect(() => { 
    // 添加事件监听 
    document.addEventListener("click", handleClick, true);
    return () => { 
    // 移除事件监听 
    document.removeEventListener("click", handleClick, true) } 
}, []) 

const handleClick = () => { 
    // 执行一些读取state的操作 
    console.log(count); 
    setCount(preValue => {
        // 执行一些读取state的操作 
        console.log(preValue); 
        return preValue+1;
   }); 
}

image.png