日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

React?Hook?四種組件優化總結_React

作者:??河畔一角? ? 更新時間: 2022-09-19 編程語言

前言

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

欄目分類
最近更新