详解如何在React中优雅的使用addEventListener
在 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;依赖项中放入 state,state 变化后就不是同一个事件函数了,无法移除事件
如何解决这个问题呢?
把事件函数保存为状态:
- 当
count变化时,挂载事件,同时将事件函数保存为state - 当
eventFn.fn变化时,在useEffect return中卸载之前的事件函数(这里利用的是闭包)
具体的代码:
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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
React Hooks之useDeferredValue钩子用法示例详解
useDeferredValue钩子的主要目的是在React的并发模式中提供更流畅的用户体验,特别是在有高优先级和低优先级更新的情况下,本文主要讲解一些常见的使用场景及其示例2023-09-09
详解React中的useMemo和useCallback的区别
React中的useMemo和useCallback是两个重要的Hooks。常常被用于优化组件的性能。虽然这两个Hooks看起来很相似,但它们彼此之间还是有很大的区别的,随着小编一起来学习吧2023-04-04
react 报错Module build failed: Browserslis
这篇文章主要介绍了react 报错Module build failed: BrowserslistError: Unknown browser query `dead`问题的解决方法,需要的朋友可以参考下2023-06-06
react router4+redux实现路由权限控制的方法
本篇文章主要介绍了react router4+redux实现路由权限控制的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧2018-05-05
JavaScript的React框架中的JSX语法学习入门教程
这篇文章主要介绍了JavaScript的React框架中的JSX语法学习入门教程,React是由Facebook开发并开源的高人气js框架,需要的朋友可以参考下2016-03-03
浅谈React的React.FC与React.Component的使用
本文主要介绍了React的React.FC与React.Component的使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下2021-09-09
react执行【npx create-react-app my-app】出现常见错误的解决办法
文章主要介绍了在使用npx创建React应用时可能遇到的几种常见错误及其解决方法,包括缺少依赖、网络问题和npx解析错误等,并提供了相应的解决措施,此外,还提到了使用腾讯云云产品来支持React应用开发2024-11-11


最新评论