網(wǎng)站首頁 編程語言 正文
前言
React Hook 已成為當(dāng)前最流行的開發(fā)范式,React 16.8 以后基于 Hook 開發(fā)極大簡化開發(fā)者效率,同時(shí)不正確的使用 React Hook也帶來了很多的性能問題,本文梳理基于 React Hook 開發(fā)組件的過程中如何提高性能。
組件抽取
優(yōu)化前
每次點(diǎn)擊?Increase
?都會(huì)引起子組件?Child
?的渲染,哪怕子組件并沒有狀態(tài)變化
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>計(jì)數(shù)器:</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> ????) }
優(yōu)化后
只需要把?Increase
?抽取成獨(dú)立的組件即可。此時(shí)點(diǎn)擊按鈕,子組件并不會(huì)渲染。
/** ?*?優(yōu)化后,Increase提取以后,上下文發(fā)生變化,組件內(nèi) ?*?@returns? ?*/ function?Increase(){ ????console.log('Child?Increase') ????let?[count,setCount]?=?useState(0) ????const?handleClick?=?()=>{ ????????setCount(count+1) ????} ????return?( ????????<div> ????????????<div?className='l50'> ????????????????<label>計(jì)數(shù)器:</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 優(yōu)化組件
同樣基于上述優(yōu)化前代碼,如果不抽取組件,使用?memo
?優(yōu)化后,當(dāng)點(diǎn)擊按鈕后,也不會(huì)觸發(fā)二次渲染。
//?優(yōu)化前 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>計(jì)數(shù)器:</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?方法的返回結(jié)果與 ??將?prevProps?傳入?render?方法的返回結(jié)果一致則返回?true, ??否則返回?false ??*/ } export?default?React.memo(TestComponent,areEqual)
與 class 組件中 shouldComponentUpdate() 方法不同的是,如果 props 相等,areEqual 會(huì)返回 true;如果 props 不相等,則返回 false。這與 shouldComponentUpdate 方法的返回值相反。
useCallback 優(yōu)化組件
如果已經(jīng)用了?memo
?,當(dāng)遇到下面這種場景時(shí),同樣會(huì)觸發(fā)子組件渲染。比如,給?Child
?綁定一個(gè)?handleClick
?,子組件內(nèi)部增加一個(gè)按鈕,當(dāng)點(diǎn)擊子組件的按鈕時(shí),更改?count
?值,即使沒有發(fā)生?name
?變化,也同樣會(huì)觸發(fā)子組件渲染,為什么?memo
?不是會(huì)判斷?name
?變化了,才會(huì)更新嗎?
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>計(jì)數(shù)器:</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
?沒有生效,是因?yàn)楫?dāng)狀態(tài)發(fā)生變化時(shí),父組件會(huì)從新執(zhí)行,導(dǎo)致從新創(chuàng)建了新的handleChange
?函數(shù),而?handleChange
?的變化導(dǎo)致了子組件的再次渲染。
優(yōu)化后
點(diǎn)擊父組件的Increase
按鈕,更改了?count
?值,經(jīng)過?useCallback
?包裹?handleChange
?函數(shù)以后,我們會(huì)發(fā)現(xiàn)子組件不再渲染,說明每當(dāng)父組件執(zhí)行的時(shí)候,并沒有創(chuàng)建新的?handleChange
?函數(shù),這就是通過?useCallback
?優(yōu)化后的效果。 即使我們點(diǎn)擊子組件的按鈕,也同樣不會(huì)觸發(fā)子組件的渲染,同樣?count
?會(huì)進(jìn)行累加。
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>計(jì)數(shù)器:</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(){ ??//?點(diǎn)擊按鈕調(diào)用此函數(shù),但返回被緩存 ??const?onClick?=?useCallback(()?=>?{ ????console.log('我被緩存了,怎么點(diǎn)擊都返回一樣'); ??},?[]); ??return?(? ????<button?onClick={onClick}>點(diǎn)擊</button> ??); }
-
useCallback
?接收 2 個(gè)參數(shù),第一個(gè)為緩存的函數(shù),第二個(gè)為依賴值 - 主要用于緩存函數(shù),第二次會(huì)返回同樣的結(jié)果。
useMemo 優(yōu)化
我們定義了一個(gè)total
函數(shù),內(nèi)部使用 1 填充了100次,通過?reduce
?計(jì)算總和,經(jīng)過測試發(fā)現(xiàn)點(diǎn)擊?Increase
按鈕后,只會(huì)執(zhí)行?total1
?,不會(huì)執(zhí)行?total2
,假設(shè)total
計(jì)算量巨大,就會(huì)造成內(nèi)存的浪費(fèi),通過?useMemo
?可以幫我們緩存計(jì)算值。
function?Before(){ ????console.log('Demo1?Parent') ????let?[count,setCount]?=?useState(0) ????const?handleClick?=?()=>{ ????????setCount(count+1) ????} ????const?total1?=?()=>{ ????????console.log('計(jì)算求和1') ????????let?arr?=?Array.from({?length:100?}).fill(1) ????????return?arr.reduce((prev,next)=>prev+next,0) ????} ????//?緩存對(duì)象值 ????const?total2?=?useMemo(()=>{ ????????console.log('計(jì)算求和2') ????????let?arr?=?Array.from({?length:100?}).fill(1) ????????return?arr.reduce((prev,next)=>prev+next,0) ????},[count]) ????return?( ????????<div> ????????????<div?className='l50'> ????????????????<label>計(jì)數(shù)器:</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]);
- 傳入一個(gè)函數(shù)進(jìn)去,會(huì)返回一個(gè)?
memoized
?值,需要注意的是,函數(shù)內(nèi)必須有返回值 - 第二個(gè)參數(shù)會(huì)依賴值,當(dāng)依賴值更新時(shí),會(huì)從新計(jì)算。
useCallback 和 useMemo 區(qū)別
他們都用于緩存,useCallback
?主要用于緩存函數(shù),返回一個(gè) 緩存后 函數(shù),而?useMemo
?主要用于緩存值,返回一個(gè)緩存后的值。
原文鏈接:https://juejin.cn/post/7124338348885278757
相關(guān)推薦
- 2022-09-30 Ajax實(shí)現(xiàn)關(guān)鍵字聯(lián)想和自動(dòng)補(bǔ)全功能及遇到坑_AJAX相關(guān)
- 2023-10-15 字符串逆序輸出,編譯器優(yōu)化,循環(huán)代碼外提,無效代碼刪除
- 2023-01-31 MongoDB?聚合查詢?cè)斀鈅MongoDB
- 2022-04-23 C語言Static?關(guān)鍵字解析_C 語言
- 2022-10-29 微服務(wù)啟動(dòng)報(bào)錯(cuò):No Feign Client for loadBalancing defined.
- 2022-05-19 ASP.NET?Core框架探索之Authentication的權(quán)限認(rèn)證過程解析_實(shí)用技巧
- 2022-12-13 React?setState是異步還是同步原理解析_React
- 2022-08-12 Android自定義彈出框的方法_Android
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支