網站首頁 編程語言 正文
熱身準備
useCallback
和useMemo
是一樣的東西,只是入參有所不同。
useCallback
緩存的是回調函數,如果依賴項沒有更新,就會使用緩存的回調函數;
useMemo
緩存的是回調函數的return
,如果依賴項沒有更新,就會使用緩存的return
;
官網有這樣一段描述useCallback(fn, deps)
相當于useMemo(() => fn, deps)
。
所以這里,只以useCallback
為例進行分析。
初始化mount
mountCallback
如果各位看官是系列文章第一篇開始看的,看到這里估計就無壓力,mountCallback
就這幾行代碼,筆者沒有做精簡。
function mountCallback(callback, deps) { // 初始化hook結構 var hook = mountWorkInProgressHook(); // 使用者傳進來的依賴數組 var nextDeps = deps === undefined ? null : deps; // 以數組的形式將回調和依賴數組存儲到對應fiber.memoizedState.hook.moeoizedState hook.memoizedState = [callback, nextDeps]; return callback; }
更新 update
function updateCallback(callback, deps) { var hook = updateWorkInProgressHook(); var nextDeps = deps === undefined ? null : deps; var prevState = hook.memoizedState; if (prevState !== null) { if (nextDeps !== null) { var prevDeps = prevState[1]; if (areHookInputsEqual(nextDeps, prevDeps)) { return prevState[0]; } } } hook.memoizedState = [callback, nextDeps]; return callback; }
updateCallback
就這幾行代碼,沒有刪減,代碼意圖也很簡單,如果依賴數組deps
沒有變化,或者deps=[]
的情況下,會返回之前緩存的回調函數,否則就更新對應fiber.memoizedState.hook.memoizedState
并返回新的回調函數。
使用場景
就筆者的所見所聞,存在兩種極端情況,一種開發者在開發時,不管什么函數,什么數據都喜歡使用useCallback
,useMemo
進行一層包裹。還有一種開發者不管什么情況都不會考慮使用useCallback
,useMemo
。
不用說,這兩種做法都是有問題的。第一種做法,還不知道是之所以會出現這樣的問題,根本原因還是很多開發者并不明白這兩個hook
的原理和使用場景。
首先,我們要明確函數組件在每一次更新時,都會執行函數組件,函數組件內部的所有方法,所有值都會重新聲明,重新計算。這兩個hook
的出現就是為了優化這種情況,避免不必要的浪費。而這兩個hook
的做法就是通過將函數或者值存儲在對應的fiber.memoizedState.hook.memoizedState
上,在下次更新時,根據依賴項是否變化來決定是否要用緩存值,還是新的傳進來的值。
這時候可能有人疑惑既然都會更新,那我全部包裹起來有什么不好?筆者認為都進行包裹主要的問題是,如果一個函數足夠簡單,從新聲明可能性能消耗會比包裹后存儲在hook.memoizedState
的消耗更小。
這里,筆者根據自己看源碼的心得,列舉下這兩個hook
的使用場景:
- 如果子組件比較復雜,可以考慮使用
useCallback
進行包裹; - 如果函數組件中某個值需要大量的計算才能得出,可以考慮使用
useMemo
進行包裹; - 如果某個函數是子組件的props,可以考慮使用
useCallback
進行包裹(配合React.memo
使用); - 自定義
hooks
中復雜邏輯可以考慮使用useCallback
和useMemo
進行包裹;
相關參考視頻講解:傳送門
總結
這兩個hook
原理還是很簡單的,因為是系列文章,很多內容和前面文章都重復了,所以導致這篇都沒啥能寫的了。總結下原理:
這兩個hook
的做法就是通過將函數或者值存儲在對應的fiber.memoizedState.hook.memoizedState
上,在下次更新時,根據依賴項是否變化來決定是要用緩存值,還是新的傳進來的值。
雖然useCallback
和useMemo
是為了優化性能出現的,但是各位看官也不要盲目使用,畢竟這兩個hook
本身也會帶來開銷。
看完這篇文章, 我們可以弄明白下面這幾個問題:
-
useCallback
和useMemo
的區別? -
useCallback
和useMemo
的使用場景有哪些? -
useCallback
和useMemo
是做什么的? -
useCallback
和useMemo
是怎么實現優化性能的?
熱身準備
useContext
可以幫助我們跨越組件層級直接傳遞變量,避免了在每一個層級手動的傳遞 props 屬性,實現共享,要配合createContext
使用。
createContext
createContext
主要功能是創建一個context
,提供Provider
和Consumer
。Provider
主要將context
內容暴露出來,Consumer
可以拿到對應context
的Provider
暴露的內容使用。
示例代碼:
export const Context = createContext(null) <Context.Provider value='initialValue'> <Context.Consumer> {(v) => { return <h2>{v}</h2> }} </Context.Consumer> </Context.Provider>
Provider
<Context.Provider>
在渲染時,beginWork
階段,會執行
pushProvider(workInProgress, newValue);
它會將Provider
的prop
上的value
字段存到context._currentValue
中。
Consumer
<Context.Consumer>
在渲染時,beginWork
階段,會執行
prepareToReadContext(workInProgress, renderLanes); var newValue = readContext(context, newProps.unstable_observedBits);
通過上面代碼可以拿到Provider
的prop
上的value
。
值得注意的是, Consumer
標簽下包裹的必須是一個函數,如果不是函數會報錯。 Consumer
會將拿到的value
作為函數的參數傳入函數中去使用。如同上面示例代碼中獲取到的v
。
useContext
useContext
需要將createContext
創建的Context
作為參數進行調用。
值得一提的是,前面講的hook
在初始化和更新時會有兩套不同函數執行。但是在useContext
只有一個,也就是useContext
在初始化和更新時執行的是一套代碼。
初始化mount&更新update
useContext
在mount
時主要會調用readContext
函數:
function readContext(context, observedBits) { var contextItem = { context: context, // 傳入的context observedBits: resolvedObservedBits, // 觀察范圍(默認全部update) next: null }; lastContextDependency = contextItem; currentlyRenderingFiber.dependencies = { lanes: NoLanes, firstContext: contextItem, responders: null }; } else { // Append a new context item. lastContextDependency = lastContextDependency.next = contextItem; } return context._currentValue ; }
精簡了下代碼,可以看到,readContext
會創建一個contextItem
并以鏈表的結構記錄在對應fiber.dependencies
上,最后將Provider
的prop
上的value
返回。
總結
useContext
的原理類似于觀察者模式。Provider
是被觀察者, Consumer
和useContext
是觀察者。當Provider
上的值發生變化, 觀察者是可以觀察到的,從而同步信息給到組件。
主要使用場景就是多層級組件值的傳遞,如果值較多可以考慮配合useReducer
使用。
看完這篇文章, 我們可以弄明白下面這個問題:
useContext的原理是什么?
原文鏈接:https://blog.csdn.net/It_kc/article/details/127569061
相關推薦
- 2022-07-12 Hive獲取當天0點時間,條件查詢某一天數據
- 2023-02-02 Nginx顯示500錯誤的原因以及解決方法_nginx
- 2022-12-04 python亂序字符串排序的實現方式_python
- 2022-05-31 python字典中get()函數的基本用法實例_python
- 2022-08-12 python封裝成exe的超詳細教程_python
- 2022-08-29 .NET?Core使用Eureka實現服務注冊_實用技巧
- 2023-05-31 pandas.DataFrame的for循環迭代的實現_python
- 2022-10-26 Golang?Mutex?原理詳細解析_Golang
- 最近更新
-
- 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同步修改后的遠程分支