当前位置: 首页 > Web前端 > JavaScript

React中如何优雅的使用addEventListener

时间:2023-03-27 17:12:31 JavaScript

在ReactHooks中使用第三方库的事件时,很多人会这样写(指我):const[count,setCount]=useState(0);useEffect(()=>{constlibrary=newLibrary();library.on("click",()=>{console.log(count);//无法获取最新计数});},[]);这样写会有问题:只会在这个组件加载的时候,绑定事件。如果在该事件中使用了其他状态,当状态发生变化时,事件中将无法获取到最新的状态。你会认为我把state放在了dependencies里面:const[count,setCount]=useState(0);useEffect(()=>{constlibrary=newLibrary();//点击事件会重复绑定库on("click",()=>{console.log(count);});},[count]);这样又会出现新的问题:点击事件会被重复绑定。这个时候你说我先卸载点击事件,绑定事件:const[count,setCount]=useState(0);useEffect(()=>{constlibrary=newLibrary();library.on("click",handleClick);return()=>{//事件无法卸载,handleClick&&库仍会重复绑定un("click",handleClick);};},[count]);consthandleClick=()=>{console.log(count);};你惊奇地发现,之前的事件无法卸载,但仍然会重复绑定事件。如何解决这个问题呢?使用addEventListener代替第三方库事件这里使用addEventListener而不是第三方库的事件,初始代码constTest=(props)=>{constref=useRef();const[count,setCount]=useState(0);useEffect(()=>{consthandleClick=(event)=>{console.log("clicked");console.log("count",count);};constelement=ref.current;element.addEventListener("点击",handleClick);return()=>{element.removeEventListener("click",handleClick);};},[]);constonClickIncrement=()=>{setCount(count+1);};return(<>

测试

点击+1
count:{count}
点击测试按钮);};方法一:状态改变,卸载/绑定事件将状态放在依赖中,需要解决状态改变时重复绑定事件的问题。解决事件重复绑定的问题,首先想到的就是事件卸载。(()=>{handleClick&&ref.current.removeEventListener("click",handleClick);ref.current.addEventListener("click",handleClick);},[count]);consthandleClick=()=>{console.log(count);};这是ReactHooks中的一个坑,它会重新声明handleClick事件函数。新的handleClick和之前的handleClick不是事件函数。removeEventListener移除的事件函数不是之前的事件函数。那你又会想,我加useCallbackuseEffect(()=>{handleClicktohandleClick&&ref.current.removeEventListener("click",handleClick);ref.current.addEventListener("click",handleClick);},[count]);consthandleClick=useCallback(()=>{console.log(count);},[]);如果你这样写,你还是会遇到同样的问题:如果依赖是一个空数组,你将无法获取到最新的状态;如果把state放在dependency里,state改变后就不是同一个事件函数了。如何删除事件来解决这个问题?将事件函数保存为状态:当计数发生变化时,挂载事件,同时将事件函数保存为状态。当eventFn.fn变化时,卸载useEffect中之前的事件函数return(这里使用闭包)具体代码:constTest=()=>{constref=useRef();const[count,setCount]=useState(0);const[eventFn,setEventFn]=useState({fn:null});useEffect(()=>{mountEvent();},[count]);常数mountEvent=()=>{如果(!ref.current)返回;//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]);常量handleClick=()=>{console.log(count);};constonClickIncrement=()=>{setCount(count+1);};return(<>

测试

click+1
count:{count}
点击测试按钮);};方法二:使用闭包卸载事件使用闭包,可以简化方法一constTest=()=>{constref=useRef();const[count,setCount]=useState(0);useEffect(()=>{常量元素=参考电流;element.addEventListener("点击",handleClick);return()=>{element.removeEventListener("click",handleClick);};},[数数]);常量handleClick=()=>{console.log(count);};constonClickIncrement=()=>{setCount(count+1);};return(<>

测试

click+1
count:{count}
点击测试按钮);};useEffect中的变量返回使用闭包,这在刚开始学习的时候,很难理解方法三:使用ref保存状态。ref保存的数据虽然不能用于页面渲染,但是可以作为状态备份。当状态改变时,更新事件函数中的ref,得到最新的stateRefconstTest=()=>{constref=useRef();const[count,setCount]=useState(0);constcountRef=useRef(计数);useEffect(()=>{countRef.current=count;},[count]);useEffect(()=>{constelement=ref.current;element.addEventListener("click",handleClick);},[]);consthandleClick=()=>{console.log(countRef.current);};constonClickIncrement=()=>{setCount(count+1);};return(<>

测试

点击+1
count:{count}
点击测试按钮);};优化状态手动维护上面三种方法,有个问题,需要手动维护的状态如何优化?方法一和方法二同样优化:将依赖计数改为stateconst[state,setState]=useState({count:0});useEffect(()=>{//...},[state]);第三种方法的优化是用stateRef保存ref对象,当state发生变化时,在事件函数const[state,setState]=useState({count:0})中使用stateRef遍历state给stateRef赋值;conststateRef=useRef({});useEffect(()=>{Object.keys(state).forEach((key)=>{stateRef.current[key]=state[key];});},[state]);方案四:useEvent目前还在RFC阶段constuseEvent=(handler)=>{consthandlerRef=useRef(null);使用LayoutEffect(()=>{handlerRef.current=handler;});returnuseCallback((...args)=>{constfn=handlerRef.current;returnfn(...args);},[]);};方案五:useMemoizedFnahookimplementfunctionuseMemoizedFn(fn){constfnRef=useRef(fn);fnRef.current=useMemo(()=>fn,[fn]);constmemoizedFn=useRef();如果(!memoizedFn.current){memoizedFn.current=function(_this,...args){returnfnRef.current.apply(_this,args);};}returnmemoizedFn.current;}写在最后这个问题困扰了我很久,写业务的时候一直在用方法1,依赖越来越多,维护是噩梦(方法3也是一个恶梦)。我一直在思考如何在addEventListener中获取最新状态。今天想记录一下方法一和方法三,在稍微记笔记的时候发现了方法二,可以使用闭包来解决事件卸载的问题,从而找到了优化和维护依赖的方法。如果我今天不写笔记,这个问题会一直困扰着我。