React?Hook?四種組件優(yōu)化總結(jié)
前言
React Hook 已成為當(dāng)前最流行的開(kāi)發(fā)范式,React 16.8 以后基于 Hook 開(kāi)發(fā)極大簡(jiǎn)化開(kāi)發(fā)者效率,同時(shí)不正確的使用 React Hook也帶來(lái)了很多的性能問(wèn)題,本文梳理基于 React Hook 開(kāi)發(fā)組件的過(guò)程中如何提高性能。
組件抽取
優(yōu)化前
每次點(diǎn)擊 Increase 都會(huì)引起子組件 Child 的渲染,哪怕子組件并沒(méi)有狀態(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 語(yǔ)法
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)遇到下面這種場(chǎng)景時(shí),同樣會(huì)觸發(fā)子組件渲染。比如,給 Child 綁定一個(gè) handleClick ,子組件內(nèi)部增加一個(gè)按鈕,當(dāng)點(diǎn)擊子組件的按鈕時(shí),更改 count 值,即使沒(méi)有發(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 沒(méi)有生效,是因?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)過(guò) useCallback 包裹 handleChange 函數(shù)以后,我們會(huì)發(fā)現(xiàn)子組件不再渲染,說(shuō)明每當(dāng)父組件執(zhí)行的時(shí)候,并沒(méi)有創(chuàng)建新的 handleChange 函數(shù),這就是通過(guò) 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è)為依賴(lài)值- 主要用于緩存函數(shù),第二次會(huì)返回同樣的結(jié)果。
useMemo 優(yōu)化
我們定義了一個(gè)total函數(shù),內(nèi)部使用 1 填充了100次,通過(guò) reduce 計(jì)算總和,經(jīng)過(guò)測(cè)試發(fā)現(xiàn)點(diǎn)擊 Increase按鈕后,只會(huì)執(zhí)行 total1 ,不會(huì)執(zhí)行 total2,假設(shè)total計(jì)算量巨大,就會(huì)造成內(nèi)存的浪費(fèi),通過(guò) 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 語(yǔ)法
const?memoizedValue?=?useMemo(()?=>?computeExpensiveValue(a,?b),?[a,?b]);
- 傳入一個(gè)函數(shù)進(jìn)去,會(huì)返回一個(gè)
memoized值,需要注意的是,函數(shù)內(nèi)必須有返回值 - 第二個(gè)參數(shù)會(huì)依賴(lài)值,當(dāng)依賴(lài)值更新時(shí),會(huì)從新計(jì)算。
useCallback 和 useMemo 區(qū)別
他們都用于緩存,useCallback 主要用于緩存函數(shù),返回一個(gè) 緩存后 函數(shù),而 useMemo 主要用于緩存值,返回一個(gè)緩存后的值。
到此這篇關(guān)于React Hook 四種組件優(yōu)化總結(jié)的文章就介紹到這了,更多相關(guān)React Hook 組件優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React數(shù)據(jù)傳遞之組件內(nèi)部通信的方法
這篇文章主要介紹了React數(shù)據(jù)傳遞之組件內(nèi)部通信的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12
解決React報(bào)錯(cuò)`value` prop on `input` should&
這篇文章主要為大家介紹了React報(bào)錯(cuò)`value` prop on `input` should not be null解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
一看就懂的ReactJs基礎(chǔ)入門(mén)教程-精華版
現(xiàn)在最熱門(mén)的前端框架有AngularJS、React、Bootstrap等。自從接觸了ReactJS,ReactJs的虛擬DOM(Virtual DOM)和組件化的開(kāi)發(fā)深深的吸引了我,下面來(lái)跟我一起領(lǐng)略ReactJs的風(fēng)采吧~~ 文章有點(diǎn)長(zhǎng),耐心讀完,你會(huì)有很大收獲哦2021-04-04
React的createElement和render手寫(xiě)實(shí)現(xiàn)示例
這篇文章主要為大家介紹了React的createElement和render手寫(xiě)實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08

