文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

详解如何在React中优雅的使用addEventListener

2023-01-31 12:03

关注

在 React Hooks 中使用第三方库的事件时,很多人会写成这样(指的就是我):

const [count, setCount] = useState(0);
useEffect(() => {
  const library = new Library();
  library.on("click", () => {
    console.log(count); // 拿不到最新的 count
  });
}, []);

这样写会有问题:

它只会在这个组件加载时,绑定事件,如果这个事件中用到了其他的 state,那么这个状态发生变化时事件中是拿不到最新的 state

你会想到,我把 state 放到依赖项中:

const [count, setCount] = useState(0);
useEffect(() => {
  const library = new Library();
  // click 事件会重复绑定
  library.on("click", () => {
    console.log(count);
  });
}, [count]);

这样做又会有新问题:click 事件会重复绑定

这时候你说那我先卸载 click 事件,在绑定事件:

const [count, setCount] = useState(0);
useEffect(() => {
  const library = new Library();
  library.on("click", handleClick);
  return () => {
    // 卸载不掉事件,还是会重复绑定
    handleClick && library.un("click", handleClick);
  };
}, [count]);

const handleClick = () => {
  console.log(count);
};

你惊奇的发现,居然卸载不掉之前的事件,还是会重复绑定事件。

如何解决这个问题呢?

使用 addEventListener 代替第三方库的事件

这里使用 addEventListener 代替第三方库的事件,初始代码

const Test = (props) => {
  const ref = useRef();
  const [count, setCount] = useState(0);

  useEffect(() => {
    const handleClick = (event) => {
      console.log("clicked");
      console.log("count", count);
    };
    const element = ref.current;
    element.addEventListener("click", handleClick);
    return () => {
      element.removeEventListener("click", handleClick);
    };
  }, []);

  const onClickIncrement = () => {
    setCount(count + 1);
  };
  return (
    <>
      <h2>Test</h2>
      <button onClick={onClickIncrement}>点击 +1</button>
      <div>count: {count}</div>
      <button ref={ref}>Click Test Button</button>
    </>
  );
};

方法一:state 变化,卸载/绑定事件

将 state 放在依赖项中,就要解决 state 变化时,事件重复绑定的问题

解决事件重复绑定问题,首先想到的是事件卸载

你很容易就会想到这样写

useEffect(() => {
  handleClick && ref.current.removeEventListener("click", handleClick);
  ref.current.addEventListener("click", handleClick);
}, [count]);

const handleClick = () => {
  console.log(count);
};

这在 React Hooks 中是一个坑,state 变化后会 handleClick 事件函数会重新声明,新的 handleClick 和之前的 handleClick 不是一个事件函数,导致 removeEventListener 移除的事件函数不是之前的事件函数

那你又会想到,我给 handleClick 加个 useCallback

useEffect(() => {
  handleClick && ref.current.removeEventListener("click", handleClick);
  ref.current.addEventListener("click", handleClick);
}, [count]);

const handleClick = useCallback(() => {
  console.log(count);
}, []);

这样写的话还是会有同一个问题:依赖项为空数组,就拿不到最新的 state;依赖项中放入 statestate 变化后就不是同一个事件函数了,无法移除事件

如何解决这个问题呢?

把事件函数保存为状态:

具体的代码:

const Test = () => {
  const ref = useRef();
  const [count, setCount] = useState(0);
  const [eventFn, setEventFn] = useState({ fn: null });

  useEffect(() => {
    mountEvent();
  }, [count]);

  const mountEvent = () => {
    if (!ref.current) return;
    //  eventFn.fn && ref.current.removeEventListener("click", eventFn.fn);  // 下面看不懂的话,也可以这样写
    ref.current.addEventListener("click", handleClick);
    setEventFn({ fn: handleClick });
  };

  useEffect(() => {
    return () => {
      eventFn.fn && ref.current.removeEventListener("click", eventFn.fn); // 这里用的是闭包,和上面注释部分任选其一
    };
  }, [eventFn.fn]);

  const handleClick = () => {
    console.log(count);
  };

  const onClickIncrement = () => {
    setCount(count + 1);
  };

  return (
    <>
      <h2>Test</h2>
      <button onClick={onClickIncrement}>点击 +1</button>
      <div>count: {count}</div>
      <button ref={ref}>Click Test Button</button>
    </>
  );
};

方法二:使用闭包的方式卸载事件

利用闭包,可以将方法一简化

const Test = () => {
  const ref = useRef();
  const [count, setCount] = useState(0);

  useEffect(() => {
    const element = ref.current;
    element.addEventListener("click", handleClick);
    return () => {
      element.removeEventListener("click", handleClick);
    };
  }, [count]);

  const handleClick = () => {
    console.log(count);
  };

  const onClickIncrement = () => {
    setCount(count + 1);
  };

  return (
    <>
      <h2>Test</h2>
      <button onClick={onClickIncrement}>点击 +1</button>
      <div>count: {count}</div>
      <button ref={ref}>Click Test Button</button>
    </>
  );
};

useEffect return 中的变量用的是闭包,这点刚开始学的时候不好理解

方法三:使用 ref 保存状态

ref 保存的数据虽然不能用于页面渲染,但可以作为 state 备份,在 state 变化时更新 ref

在事件函数中就能拿到最新的 stateRef

const Test = () => {
  const ref = useRef();
  const [count, setCount] = useState(0);

  const countRef = useRef(count);
  useEffect(() => {
    countRef.current = count;
  }, [count]);

  useEffect(() => {
    const element = ref.current;
    element.addEventListener("click", handleClick);
  }, []);

  const handleClick = () => {
    console.log(countRef.current);
  };

  const onClickIncrement = () => {
    setCount(count + 1);
  };

  return (
    <>
      <h2>Test</h2>
      <button onClick={onClickIncrement}>点击 +1</button>
      <div>count: {count}</div>
      <button ref={ref}>Click Test Button</button>
    </>
  );
};

优化 state 手动维护

上面三种方法,都有个问题,state 需要手动维护

这一步如何优化呢?

方法一和方法二,优化的方式都一样:将依赖项是 count 改为 state

const [state, setState] = useState({ count: 0 });

useEffect(() => {
  // ...
}, [state]);

方法三的优化是,用 stateRef 保存 ref 对象,当 state 变化时,遍历 state 给 stateRef 赋值

事件函数中使用 stateRef

const [state, setState] = useState({ count: 0 });
const stateRef = useRef({});
useEffect(() => {
  Object.keys(state).forEach((key) => {
    stateRef.current[key] = state[key];
  });
}, [state]);

到此这篇关于详解如何在React中优雅的使用addEventListener的文章就介绍到这了,更多相关React addEventListener内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯