網站首頁 編程語言 正文
前言
React Hook 已成為當前最流行的開發范式,React 16.8 以后基于 Hook 開發極大簡化開發者效率,同時不正確的使用 React Hook也帶來了很多的性能問題,本文梳理基于 React Hook 開發組件的過程中如何提高性能。
組件抽取
優化前
每次點擊?Increase
?都會引起子組件?Child
?的渲染,哪怕子組件并沒有狀態變化
function?Before(){ ????console.log('Demo1?Parent') ????let?[count,setCount]?=?useState(0) ????let?[name,setName]?=?useState('-') ????const?handleClick?=?()=>{ ????????setCount(count+1) ????} ????const?handleInput?=?(e)=>{ ????????setName(e.target.value) ????} ????return?( ????????<div> ????????????<div?className='l50'> ????????????????<label>計數器:</label> ????????????????<span?className='mr10'>{count}</span> ????????????????<button?className='ml10'?onClick={handleClick}>Increase</button> ????????????</div> ????????????<div?className='l50'> ????????????????<label?htmlFor="">改變子組件:</label> ????????????????<input?type="text"?onChange={handleInput}/> ????????????</div> ????????????<hr?/> ????????????<Child?name={name}/> ????????</div> ????) } //?子組件 function?Child(props){ ????console.log('Demo1?Child') ????return?( ????????<div?className='l50'> ????????????子組件渲染:{props.name} ????????</div> ????) }
優化后
只需要把?Increase
?抽取成獨立的組件即可。此時點擊按鈕,子組件并不會渲染。
/** ?*?優化后,Increase提取以后,上下文發生變化,組件內 ?*?@returns? ?*/ function?Increase(){ ????console.log('Child?Increase') ????let?[count,setCount]?=?useState(0) ????const?handleClick?=?()=>{ ????????setCount(count+1) ????} ????return?( ????????<div> ????????????<div?className='l50'> ????????????????<label>計數器:</label> ????????????????<span?className='mr10'>{count}</span> ????????????????<button?className='ml10'?onClick={handleClick}>Increase</button> ????????????</div> ????????</div> ????) } function?After(){ ????console.log('Demo1?Parent') ????let?[name,setName]?=?useState('-') ????const?handleInput?=?(e)=>{ ????????setName(e.target.value) ????} ????return?( ????????<div> ????????????<Increase/> ????????????<div?className='l50'> ????????????????<label?htmlFor="">改變子組件:</label> ????????????????<input?type="text"?onChange={handleInput}/> ????????????</div> ????????????<Child?name={name}/> ????????</div> ????) } //?子組件 function?Child(props){ ????console.log('Demo1?Child') ????return?( ????????<div?className='l50'> ????????????子組件渲染:{props.name} ????????</div> ????) }
memo 優化組件
同樣基于上述優化前代碼,如果不抽取組件,使用?memo
?優化后,當點擊按鈕后,也不會觸發二次渲染。
//?優化前 function?AfterMemo(){ ????console.log('Demo1?Parent') ????let?[count,setCount]?=?useState(0) ????let?[name,setName]?=?useState('-') ????const?handleClick?=?()=>{ ????????setCount(count+1) ????} ????const?handleInput?=?(e)=>{ ????????setName(e.target.value) ????} ????return?( ????????<div> ????????????<div?className='l50'> ????????????????<label>計數器:</label> ????????????????<span?className='mr10'>{count}</span> ????????????????<button?className='ml10'?onClick={handleClick}>Increase</button> ????????????</div> ????????????<div?className='l50'> ????????????????<label?htmlFor="">改變子組件:</label> ????????????????<input?type="text"?onChange={handleInput}/> ????????????</div> ????????????<Child?name={name}/> ????????</div> ????) } //?子組件 const?Child?=?memo((props)=>{ ????console.log('Demo1?Child') ????return?( ????????<div?className='l50'> ????????????子組件渲染:{props.name} ????????</div> ????) })
React.memo 語法
React.memo 為高階組件,與 React.PureComponent相似。
function?TestComponent(props){ ??//?使用?props?渲染 } function?areEqual(prevProps,nextProps){ ??/* ??如果把?nextProps?傳入?render?方法的返回結果與 ??將?prevProps?傳入?render?方法的返回結果一致則返回?true, ??否則返回?false ??*/ } export?default?React.memo(TestComponent,areEqual)
與 class 組件中 shouldComponentUpdate() 方法不同的是,如果 props 相等,areEqual 會返回 true;如果 props 不相等,則返回 false。這與 shouldComponentUpdate 方法的返回值相反。
useCallback 優化組件
如果已經用了?memo
?,當遇到下面這種場景時,同樣會觸發子組件渲染。比如,給?Child
?綁定一個?handleClick
?,子組件內部增加一個按鈕,當點擊子組件的按鈕時,更改?count
?值,即使沒有發生?name
?變化,也同樣會觸發子組件渲染,為什么?memo
?不是會判斷?name
?變化了,才會更新嗎?
function?Before(){ ????console.log('Demo1?Parent') ????let?[count,setCount]?=?useState(0) ????let?[name,setName]?=?useState('-') ????const?handleClick?=?()=>{ ????????setCount(count+1) ????} ????const?handleInput?=?(e)=>{ ????????setName(e.target.value) ????} ????const?handleChange?=?()=>{ ????????setCount(count+1) ????} ????return?( ????????<div> ????????????<div?className='l50'> ????????????????<label>計數器:</label> ????????????????<span?className='mr10'>{count}</span> ????????????????<button?className='ml10'?onClick={handleClick}>Increase</button> ????????????</div> ????????????<div?className='l50'> ????????????????<label?htmlFor="">改變子組件:</label> ????????????????<input?type="text"?onChange={handleInput}/> ????????????</div> ????????????<Child?name={name}?handleClick={handleChange}/> ????????</div> ????) } //?子組件 const?Child?=?memo((props)=>{ ????console.log('Demo1?Child') ????return?( ????????<div?className='l50'> ????????????子組件渲染:{props.name} ????????????<button?onClick={props.handleClick}>更改count</button> ????????</div> ????) })
并不是?memo
?沒有生效,是因為當狀態發生變化時,父組件會從新執行,導致從新創建了新的handleChange
?函數,而?handleChange
?的變化導致了子組件的再次渲染。
優化后
點擊父組件的Increase
按鈕,更改了?count
?值,經過?useCallback
?包裹?handleChange
?函數以后,我們會發現子組件不再渲染,說明每當父組件執行的時候,并沒有創建新的?handleChange
?函數,這就是通過?useCallback
?優化后的效果。 即使我們點擊子組件的按鈕,也同樣不會觸發子組件的渲染,同樣?count
?會進行累加。
function?After(){ ????console.log('Demo1?Parent') ????let?[count,setCount]?=?useState(0) ????let?text?=?useRef(); ????let?[name,setName]?=?useState('-') ????const?handleClick?=?()=>{ ????????setCount(count+1) ????} ????const?handleInput?=?(e)=>{ ????????setName(e.target.value) ????} ????const?handleChange?=?useCallback(()=>{ ????????//?為了讓?count?能夠累加,我們使用ref?獲取值 ????????let?val?=?parseInt(text.current.textContent); ????????setCount(val+1) ????},[]) ????return?( ????????<div> ????????????<div?className='l50'> ????????????????<label>計數器:</label> ????????????????<span?className='mr10'?ref={text}>{count}</span> ????????????????<button?className='ml10'?onClick={handleClick}>Increase</button> ????????????</div> ????????????<div?className='l50'> ????????????????<label?htmlFor="">改變子組件:</label> ????????????????<input?type="text"?value={name}?onChange={handleInput}/> ????????????</div> ????????????<Child?name={name}?handleClick={handleChange}/> ????????</div> ????) }
useCallback 作用
//?用法 useCallback(()=>{ ??//?to-do },[]) //?示例 function?App(){ ??//?點擊按鈕調用此函數,但返回被緩存 ??const?onClick?=?useCallback(()?=>?{ ????console.log('我被緩存了,怎么點擊都返回一樣'); ??},?[]); ??return?(? ????<button?onClick={onClick}>點擊</button> ??); }
-
useCallback
?接收 2 個參數,第一個為緩存的函數,第二個為依賴值 - 主要用于緩存函數,第二次會返回同樣的結果。
useMemo 優化
我們定義了一個total
函數,內部使用 1 填充了100次,通過?reduce
?計算總和,經過測試發現點擊?Increase
按鈕后,只會執行?total1
?,不會執行?total2
,假設total
計算量巨大,就會造成內存的浪費,通過?useMemo
?可以幫我們緩存計算值。
function?Before(){ ????console.log('Demo1?Parent') ????let?[count,setCount]?=?useState(0) ????const?handleClick?=?()=>{ ????????setCount(count+1) ????} ????const?total1?=?()=>{ ????????console.log('計算求和1') ????????let?arr?=?Array.from({?length:100?}).fill(1) ????????return?arr.reduce((prev,next)=>prev+next,0) ????} ????//?緩存對象值 ????const?total2?=?useMemo(()=>{ ????????console.log('計算求和2') ????????let?arr?=?Array.from({?length:100?}).fill(1) ????????return?arr.reduce((prev,next)=>prev+next,0) ????},[count]) ????return?( ????????<div> ????????????<div?className='l50'> ????????????????<label>計數器:</label> ????????????????<span?className='mr10'>{count}</span> ????????????????<button?className='ml10'?onClick={handleClick}>Increase</button> ????????????</div> ????????????<div> ????????????????<label>總和:</label> ????????????????<span>{total1()}</span> ????????????????<span>{total2}</span> ????????????</div> ????????</div> ????) }
useMemo 語法
const?memoizedValue?=?useMemo(()?=>?computeExpensiveValue(a,?b),?[a,?b]);
- 傳入一個函數進去,會返回一個?
memoized
?值,需要注意的是,函數內必須有返回值 - 第二個參數會依賴值,當依賴值更新時,會從新計算。
useCallback 和 useMemo 區別
他們都用于緩存,useCallback
?主要用于緩存函數,返回一個 緩存后 函數,而?useMemo
?主要用于緩存值,返回一個緩存后的值。
原文鏈接:https://juejin.cn/post/7124338348885278757
相關推薦
- 2022-11-04 詳解C++?指針與二維數組名_C 語言
- 2022-11-18 python標準庫?datetime的astimezone設置時區遇到的坑及解決_python
- 2022-07-09 Pytorch從0實現Transformer的實踐_python
- 2023-03-23 Android進階CoordinatorLayout協調者布局實現吸頂效果_Android
- 2024-03-20 解決npm install遇到的問題:Error while executing:
- 2022-08-29 C語言關鍵字auto與register及static專項詳解_C 語言
- 2022-08-03 利用Python連接Oracle數據庫的基本操作指南_python
- 2022-09-02 Docker資源限制Cgroup的深入理解_docker
- 最近更新
-
- 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同步修改后的遠程分支