網站首頁 編程語言 正文
在?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]);
原文鏈接:https://segmentfault.com/a/1190000043365896
相關推薦
- 2022-11-28 Git基礎學習之tag標簽操作詳解_相關技巧
- 2022-04-25 python處理SQLite數據庫的方法_python
- 2022-04-17 uniapp文本支持換行,后端返回一串文本
- 2023-01-07 Android?RecyclerBarChart繪制使用教程_Android
- 2022-03-27 C++命名空間和缺省參數介紹_C 語言
- 2022-10-03 C++模擬實現vector流程詳解_C 語言
- 2022-12-05 通過sc命令獲得System權限的代碼_DOS/BAT
- 2022-02-12 使用background-attachment實現視差滾動、水波
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支