網站首頁 編程語言 正文
引言
本文是深入淺出 ahooks 源碼系列文章的第五篇,這個系列的目標主要有以下幾點:
- 加深對 React hooks 的理解。
- 學習如何抽象自定義 hooks。構建屬于自己的 React hooks 工具庫。
- 培養閱讀學習源碼的習慣,工具庫是一個對源碼閱讀不錯的選擇。
注:本系列對 ahooks 的源碼解析是基于 v3.3.13
。自己 folk 了一份源碼,主要是對源碼做了一些解讀,可見 詳情。
本文來探索一下 ahooks 是怎么封裝 React 的一些執行“時機”的?
Function Component VS Class Component
學習類似 React 和 Vue 這種框架,對它們生命周期的掌握都是必須的,我們需要清楚的知道我們代碼的執行順序,并且在不同的階段執行不同操作的代碼,比如需要掛載完成之后才去獲取 dom 的值,否則可能會獲取不到相應的值。
Class Component
使用過 React 的 Class Component 的同學,就會知道其組件生命周期會分成三個狀態:
- Mounting(掛載):已插入真實 DOM
- Updating(更新):正在被重新渲染
- Unmounting(卸載):已移出真實 DOM
簡單版如下所示:
其中每個狀態中還會按順序調用不同的方法,對應的詳細如下(這里不展開說):
可以通過官方提供這個網站查看詳情
可以看到,會有非常多的生命周期方法,而且在不同的版本,生命周期方法還不同。
Function Component
到了 Function Component ,會發現沒有直接提及生命周期的概念,它是更徹底的狀態驅動,它只有一個狀態,React 負責將狀態渲染到視圖中。
對于 Function Component 來說由狀態到頁面渲染只有三步:
- 輸入狀態(prop、state)
- 執行組件的邏輯,并在
useEffect/useLayoutEffect
中訂閱副作用 - 輸出UI(Dom節點)
重點是第二步,React 通過 useEffect/useLayoutEffect 訂閱副作用。Class Component 中的生命周期都可以通過 useEffect/useLayoutEffect 來實現。它們兩個的功能非常相似,我們這里看下 useEffect。
使用 useEffect 相當于告訴 React 組件需要在渲染后執行某些操作,React 將在執行 DOM 更新之后調用它。React 保證了每次運行 useEffect 的時候,DOM 已經更新完畢。這就實現了 Class Component 中的 Mounting(掛載階段)。
當狀態發生變化的時候,它能夠執行對應的邏輯、更行狀態并將結果渲染到視圖中,這就完成了 Class Component 中的 Updating(更新階段)。
最后通過在 useEffect 中返回一個函數,它便可以清理副作用。它的規則是:
- 首次渲染不會進行清理,會在下一次渲染,清除上一次的副作用。
- 卸載階段也會執行清除操作。
通過返回一個函數,我們就能實現 Class Component 中的 Unmounting(卸載階段)。
基于 useEffect/useLayoutEffect,ahooks 做了一些封裝,能夠讓你更加清晰的知道你的代碼執行時機。
LifeCycle - 生命周期
useMount
只在組件初始化時執行的 Hook。 useEffect 依賴假如為空,只會在組件初始化的時候執行。
// 省略部分代碼 const useMount = (fn: () => void) => { // 省略部分代碼 // 單純就在 useEffect 基礎上封裝了一層 useEffect(() => { fn?.(); }, []); }; export default useMount;
useUnmount
useUnmount,組件卸載(unmount)時執行的 Hook。
useEffect 可以在組件渲染后實現各種不同的副作用。有些副作用可能需要清除,所以需要返回一個函數,這個函數會在組件卸載的時候執行。
const useUnmount = (fn: () => void) => { const fnRef = useLatest(fn); useEffect( // 在組件卸載(unmount)時執行的 Hook。 // useEffect 的返回值中執行函數 () => () => { fnRef.current(); }, [], ); }; export default useUnmount;
useUnmountedRef
獲取當前組件是否已經卸載的 Hook。
通過判斷有沒有執行 useEffect 中的返回值判斷當前組件是否已經卸載。
// 獲取當前組件是否已經卸載的 Hook。 const useUnmountedRef = () => { const unmountedRef = useRef(false); useEffect(() => { unmountedRef.current = false; // 如果已經卸載,則會執行 return 中的邏輯 return () => { unmountedRef.current = true; }; }, []); return unmountedRef; }; export default useUnmountedRef;
Effect
這里只會講官方文檔 Effect
下面的幾個,有部分是定時器、防抖節流等,咱們后面的系列具體分析。
useUpdateEffect 和 useUpdateLayoutEffect
useUpdateEffect 和 useUpdateLayoutEffect 的用法跟 useEffect 和 useLayoutEffect 一樣,只是會忽略首次執行,只在依賴更新時執行。
實現思路:初始化一個標識符,剛開始為 false。當首次執行完的時候,置為 true。只有標識符為 true 的時候,才執行回調函數。
// 忽略首次執行 export const createUpdateEffect: (hook: effectHookType) => effectHookType = (hook) => (effect, deps) => { const isMounted = useRef(false); // for react-refresh hook(() => { return () => { isMounted.current = false; }; }, []); hook(() => { // 首次執行完時候,設置為 true,從而下次依賴更新的時候可以執行邏輯 if (!isMounted.current) { isMounted.current = true; } else { return effect(); } }, deps); };
useDeepCompareEffect和useDeepCompareLayoutEffect
用法與 useEffect 一致,但 deps 通過 lodash isEqual 進行深比較。
通過 useRef 保存上一次的依賴的值,跟當前的依賴對比(使用 lodash 的 isEqual),并將對比結果作為 useEffect 的依賴項,從而決定回調函數是否執行。
const depsEqual = (aDeps: DependencyList, bDeps: DependencyList = []) => { return isEqual(aDeps, bDeps); }; const useDeepCompareEffect = (effect: EffectCallback, deps: DependencyList) => { // 通過 useRef 保存上一次的依賴的值 const ref = useRef<DependencyList>(); const signalRef = useRef<number>(0); // 判斷最新的依賴和舊的區別 // 如果相等,則變更 signalRef.current,從而觸發 useEffect 中的回調 if (!depsEqual(deps, ref.current)) { ref.current = deps; signalRef.current += 1; } useEffect(effect, [signalRef.current]); };
useUpdate
useUpdate 會返回一個函數,調用該函數會強制組件重新渲染。
返回的函數通過變更 useState 返回的 state,從而促使組件進行更新。
import { useCallback, useState } from 'react'; const useUpdate = () => { const [, setState] = useState({}); // 通過設置一個全新的狀態,促使 function 組件更新 return useCallback(() => setState({}), []); }; export default useUpdate;
總結與思考
在我們寫代碼的時候需要清晰的知道,組件的生命周期是怎樣的,我們代碼的執行順序、執行的時機是怎樣的。
在 Function Component 中,使用 useEffect/useLayoutEffect 完成了 Class Components 生命周期的職責。ahooks 也是基于這兩個封裝了常見的代碼執行時機,使用這些 hook,可以讓我們的代碼更加具有可讀性以及邏輯更加清晰。
原文鏈接:https://juejin.cn/post/7107189225509879838
相關推薦
- 2021-11-22 C++?STL中五個常用算法使用教程及實例講解_C 語言
- 2022-03-28 c語言經典習題之逆序字符串詳解_C 語言
- 2022-11-27 Python?OpenCV實現圖像增強操作詳解_python
- 2022-12-22 Go語言編程通過dwarf獲取內聯函數_Golang
- 2024-03-24 required a single bean, but 2 were found
- 2022-10-04 C++兩種素數判定方法_C 語言
- 2022-09-26 車載藍牙PIN碼是什么
- 2022-02-25 Linux下安裝Hadoop集群詳細步驟_Linux
- 最近更新
-
- 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同步修改后的遠程分支