詳解如何在React中優(yōu)雅的使用addEventListener
在 React Hooks
中使用第三方庫的事件時(shí),很多人會(huì)寫成這樣(指的就是我):
const [count, setCount] = useState(0); useEffect(() => { const library = new Library(); library.on("click", () => { console.log(count); // 拿不到最新的 count }); }, []);
這樣寫會(huì)有問題:
它只會(huì)在這個(gè)組件加載時(shí),綁定事件,如果這個(gè)事件中用到了其他的 state
,那么這個(gè)狀態(tài)發(fā)生變化時(shí)事件中是拿不到最新的 state
你會(huì)想到,我把 state
放到依賴項(xiàng)中:
const [count, setCount] = useState(0); useEffect(() => { const library = new Library(); // click 事件會(huì)重復(fù)綁定 library.on("click", () => { console.log(count); }); }, [count]);
這樣做又會(huì)有新問題:click
事件會(huì)重復(fù)綁定
這時(shí)候你說那我先卸載 click
事件,在綁定事件:
const [count, setCount] = useState(0); useEffect(() => { const library = new Library(); library.on("click", handleClick); return () => { // 卸載不掉事件,還是會(huì)重復(fù)綁定 handleClick && library.un("click", handleClick); }; }, [count]); const handleClick = () => { console.log(count); };
你驚奇的發(fā)現(xiàn),居然卸載不掉之前的事件,還是會(huì)重復(fù)綁定事件。
如何解決這個(gè)問題呢?
使用 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}>點(diǎn)擊 +1</button> <div>count: {count}</div> <button ref={ref}>Click Test Button</button> </> ); };
方法一:state 變化,卸載/綁定事件
將 state
放在依賴項(xiàng)中,就要解決 state
變化時(shí),事件重復(fù)綁定的問題
解決事件重復(fù)綁定問題,首先想到的是事件卸載
你很容易就會(huì)想到這樣寫
useEffect(() => { handleClick && ref.current.removeEventListener("click", handleClick); ref.current.addEventListener("click", handleClick); }, [count]); const handleClick = () => { console.log(count); };
這在 React Hooks
中是一個(gè)坑,state
變化后會(huì) handleClick
事件函數(shù)會(huì)重新聲明,新的 handleClick
和之前的 handleClick
不是一個(gè)事件函數(shù),導(dǎo)致 removeEventListener
移除的事件函數(shù)不是之前的事件函數(shù)
那你又會(huì)想到,我給 handleClick
加個(gè) useCallback
useEffect(() => { handleClick && ref.current.removeEventListener("click", handleClick); ref.current.addEventListener("click", handleClick); }, [count]); const handleClick = useCallback(() => { console.log(count); }, []);
這樣寫的話還是會(huì)有同一個(gè)問題:依賴項(xiàng)為空數(shù)組,就拿不到最新的 state
;依賴項(xiàng)中放入 state
,state
變化后就不是同一個(gè)事件函數(shù)了,無法移除事件
如何解決這個(gè)問題呢?
把事件函數(shù)保存為狀態(tài):
- 當(dāng)
count
變化時(shí),掛載事件,同時(shí)將事件函數(shù)保存為state
- 當(dāng)
eventFn.fn
變化時(shí),在useEffect return
中卸載之前的事件函數(shù)(這里利用的是閉包)
具體的代碼:
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}>點(diǎn)擊 +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}>點(diǎn)擊 +1</button> <div>count: {count}</div> <button ref={ref}>Click Test Button</button> </> ); };
useEffect return
中的變量用的是閉包,這點(diǎn)剛開始學(xué)的時(shí)候不好理解
方法三:使用 ref 保存狀態(tài)
ref
保存的數(shù)據(jù)雖然不能用于頁面渲染,但可以作為 state
備份,在 state
變化時(shí)更新 ref
在事件函數(shù)中就能拿到最新的 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}>點(diǎn)擊 +1</button> <div>count: {count}</div> <button ref={ref}>Click Test Button</button> </> ); };
優(yōu)化 state 手動(dòng)維護(hù)
上面三種方法,都有個(gè)問題,state
需要手動(dòng)維護(hù)
這一步如何優(yōu)化呢?
方法一和方法二,優(yōu)化的方式都一樣:將依賴項(xiàng)是 count
改為 state
const [state, setState] = useState({ count: 0 }); useEffect(() => { // ... }, [state]);
方法三的優(yōu)化是,用 stateRef
保存 ref
對(duì)象,當(dāng) state
變化時(shí),遍歷 state
給 stateRef
賦值
事件函數(shù)中使用 stateRef
const [state, setState] = useState({ count: 0 }); const stateRef = useRef({}); useEffect(() => { Object.keys(state).forEach((key) => { stateRef.current[key] = state[key]; }); }, [state]);
到此這篇關(guān)于詳解如何在React中優(yōu)雅的使用addEventListener的文章就介紹到這了,更多相關(guān)React addEventListener內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
antd之RangePicker設(shè)置默認(rèn)值方式
這篇文章主要介紹了antd之RangePicker設(shè)置默認(rèn)值方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12React Router 5.1.0使用useHistory做頁面跳轉(zhuǎn)導(dǎo)航的實(shí)現(xiàn)
本文主要介紹了React Router 5.1.0使用useHistory做頁面跳轉(zhuǎn)導(dǎo)航的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11react自動(dòng)化構(gòu)建路由的實(shí)現(xiàn)
這篇文章主要介紹了react自動(dòng)化構(gòu)建路由的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04React實(shí)現(xiàn)雙滑塊交叉滑動(dòng)
這篇文章主要為大家詳細(xì)介紹了React實(shí)現(xiàn)雙滑塊交叉滑動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09React?Native?的動(dòng)態(tài)列表方案探索詳解
這篇文章主要為大家介紹了React?Native?的動(dòng)態(tài)列表方案探索示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09React Hooks與setInterval的踩坑問題小結(jié)
本文主要介紹了React Hooks與setInterval的踩坑,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04react中form.setFieldvalue數(shù)據(jù)回填時(shí) value和text不對(duì)應(yīng)的問題及解決方法
這篇文章主要介紹了react中form.setFieldvalue數(shù)據(jù)回填時(shí) value和text不對(duì)應(yīng)的問題及解決方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07