前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React useEffect中使用事件监听在回调函数中state不更新的问题

React useEffect中使用事件监听在回调函数中state不更新的问题

原创
作者头像
DamonLiu
修改2022-07-31 14:14:52
10.2K0
修改2022-07-31 14:14:52
举报
文章被收录于专栏:知识技能知识技能

很多React开发者都遇到过useEffect中使用事件监听在回调函数中获取到旧的state值的问题,也都知道如何去解决。这个问题网上很多讲解都是直接讲是因为闭包导致获取到的是旧的state值,讲的不够清晰。我们看下具体的例子来逐步理解这个问题。

首先看一个手动实现的简易useEffect的事件监听的例子

import React, { useRef, useState } from 'react'; // "react": "^18.1.0",
import ReactDOM from 'react-dom/client';

let memoizedState: any[] = [];
let currentIndex = 0;

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

const App:React.FC = () => {
  const [hasAddEventListener, setHasAddEventListener] = useState(false);
  const[count, setCount] = useState(1);
  const btn = useRef<any>(null);
  
  const onAddEventListenerShowCount = () => {
    console.log('onAddEventListenerShowCount count:', count);
  }
  
  useEffect(() => {
    console.log('btn.current:', btn.current);
    btn.current?.addEventListener?.('click', onAddEventListenerShowCount)
  
    return () => {
      btn.current?.removeEventListener?.('click', onAddEventListenerShowCount)
    }
  
  },[hasAddEventListener]);
  
  const onAddEventListener = () => setHasAddEventListener(true);
  
  const onAddClick = () => {
    const newCount = count + 1;
    setCount(newCount);
    console.log('onAddClick count:', count);
    console.log('onAddClick newCount:', newCount);
  }
  
  const showCount = () => {
    console.log('showCount count:', count);
  }
  
  return (
    <div className="App">
      <div>top</div>
      <button onClick={onAddEventListener}>addEventListener</button>
      <button ref={btn}>addEventListenerShowCount</button>
      <button onClick={onAddClick}>add</button>
      <button onClick={showCount}>showCount</button>
    </div>
  );
}

// 自定义的useEffect
function useEffect(fn: any, watch: any[]) {
  if (currentIndex === 0) {
    fn();
    memoizedState[currentIndex] = watch;
    currentIndex++; // 累加 currentIndex
  }
  const hasWatchChange = memoizedState[currentIndex - 1]
    ? !watch.every((val, i) => val === memoizedState[currentIndex - 1][i])
    : true;
  console.log('hasWatchChange:', hasWatchChange)
  if (hasWatchChange) {
    fn();
    memoizedState[currentIndex] = watch;
    currentIndex++; // 累加 currentIndex
  }
}

function render() {
  root.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
  currentIndex = 0; // 注意将 effectCursor 重置为0
}

render();

渲染的页面如下

依次点击 addEventListener // 点击addEventListener按钮 添加eventListener监听事件

addEventListenerShowCount // 点击addEventListenerShowCount的按钮 eventListener事件回调函数打印state值

add // 点击add按钮 设置新的state值

showCount // 点击showCount按钮 打印state值

addEventListenerShowCount // 再次点击addEventListenerShowCount的按钮 eventListener事件回调函数打印state值

控制台打印结果如下

手动实现的简易useEffect中,事件监听回调函数中也会有获取不到state最新值的问题

下面根据上面React代码模拟为常规的js代码

let obj; // 模拟btn元素
const App = (addOne) => { // 模拟React App纯函数组件
    let a = 1; // 模拟state
    obj = obj || {
        showA: () => { // 模拟eventListener的回调函数
            console.log('obj a:', a);
        },
    }
    if (addOne) { // 模拟修改state值
        a += 1;
    }
    console.log('App a:', a);
}

全局作用域的obj对象类似于按钮btn ref

App函数类似React App纯函数组件

每次state变化,React 函数会重新执行,所以我们可以进行如下模拟操作

这个示例的运行过程就比较好理解,第一次执行App函数,初始化数据,Obj可以获取到函数内的a变量,因此,变量a所分配的内存不会释放,再运行App函数,Obj获取到的变量a始终是第一次初始化时的a在内存中指向的值。在React函数中也是一样的情况,某一个对象的监听事件的回调函数,这个对象相当于全局作用域变量(或者与函数同一层作用域链),在回调函数中获取到的state值,为第一次运行时的内存中的state值。而组件函数内的普通函数,每次运行组件函数中,普通函数与state的作用域链为同一层,所以会拿到最新的state值。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档