詳解如何在React中優(yōu)雅的使用addEventListener
在 React Hooks 中使用第三方庫(kù)的事件時(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 代替第三方庫(kù)的事件
這里使用 addEventListener 代替第三方庫(kù)的事件,初始代碼
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>
</>
);
};方法二:使用閉包的方式卸載事件
利用閉包,可以將方法一簡(jiǎn)化
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)文章
React?Hooks之useDeferredValue鉤子用法示例詳解
useDeferredValue鉤子的主要目的是在React的并發(fā)模式中提供更流暢的用戶體驗(yàn),特別是在有高優(yōu)先級(jí)和低優(yōu)先級(jí)更新的情況下,本文主要講解一些常見的使用場(chǎng)景及其示例2023-09-09
詳解React中的useMemo和useCallback的區(qū)別
React中的useMemo和useCallback是兩個(gè)重要的Hooks。常常被用于優(yōu)化組件的性能。雖然這兩個(gè)Hooks看起來很相似,但它們彼此之間還是有很大的區(qū)別的,隨著小編一起來學(xué)習(xí)吧2023-04-04
react 報(bào)錯(cuò)Module build failed: Browserslis
這篇文章主要介紹了react 報(bào)錯(cuò)Module build failed: BrowserslistError: Unknown browser query `dead`問題的解決方法,需要的朋友可以參考下2023-06-06
react router4+redux實(shí)現(xiàn)路由權(quán)限控制的方法
本篇文章主要介紹了react router4+redux實(shí)現(xiàn)路由權(quán)限控制的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05
JavaScript的React框架中的JSX語法學(xué)習(xí)入門教程
這篇文章主要介紹了JavaScript的React框架中的JSX語法學(xué)習(xí)入門教程,React是由Facebook開發(fā)并開源的高人氣js框架,需要的朋友可以參考下2016-03-03
淺談React的React.FC與React.Component的使用
本文主要介紹了React的React.FC與React.Component的使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
react執(zhí)行【npx create-react-app my-app】出現(xiàn)常見錯(cuò)誤的解決辦法
文章主要介紹了在使用npx創(chuàng)建React應(yīng)用時(shí)可能遇到的幾種常見錯(cuò)誤及其解決方法,包括缺少依賴、網(wǎng)絡(luò)問題和npx解析錯(cuò)誤等,并提供了相應(yīng)的解決措施,此外,還提到了使用騰訊云云產(chǎn)品來支持React應(yīng)用開發(fā)2024-11-11

