蘇州網(wǎng)站建設(shè)網(wǎng)搜索引擎網(wǎng)站提交入口
文章目錄
- 1.React.memo
- 2.useMemo
- 3.useCallback
- 4.useTransition
- 5.useDeferredValue
1.React.memo
當(dāng)父組件被重新渲染的時(shí)候,也會(huì)觸發(fā)子組件的重新渲染,這樣就多出了無(wú)意義的性能開(kāi)銷。如果子組件的狀態(tài)沒(méi)有發(fā)生變化,則子組件是不需要被重新渲染的。
const 組件 = React.memo(函數(shù)式組件)
父組件聲明了 count 和 flag 兩個(gè)狀態(tài),子組件依賴于父組件通過(guò) props 傳遞的 num。當(dāng)父組件修改 flag 的值時(shí),會(huì)導(dǎo)致子組件的重新渲染。
import React, { useEffect, useState } from 'react'// 父組件
export const Father: React.FC = () => {// 定義 count 和 flag 兩個(gè)狀態(tài)const [count, setCount] = useState(0)const [flag, setFlag] = useState(false)return (<><h1>父組件</h1><p>count 的值是:{count}</p><p>flag 的值是:{String(flag)}</p><button onClick={() => setCount((prev) => prev + 1)}>+1</button><button onClick={() => setFlag((prev) => !prev)}>Toggle</button><hr /><Son num={count} /></>)
}// 子組件:依賴于父組件通過(guò) props 傳遞進(jìn)來(lái)的 num
export const Son: React.FC<{ num: number }> = ({ num }) => {useEffect(() => {console.log('觸發(fā)了子組件的渲染')})return (<><h3>子組件 {num}</h3></>)
}
React.memo(函數(shù)式組件) 將子組件包裹起來(lái), 只有子組件依賴的 props 發(fā)生變化的時(shí)候, 才會(huì)觸發(fā)子組件的重新渲染。
// 子組件:依賴于父組件通過(guò) props 傳遞進(jìn)來(lái)的 num
export const Son: React.FC<{ num: number }> = React.memo(({ num }) => {useEffect(() => {console.log('觸發(fā)了子組件的渲染')})return (<><h3>子組件 --- {num}</h3></>)
})
2.useMemo
在 Father 組件中添加一個(gè)“計(jì)算屬性”, 根據(jù) flag 值的真假, 返回不同的內(nèi)容。
// 父組件
export const Father: React.FC = () => {// 定義 count 和 flag 兩個(gè)狀態(tài)const [count, setCount] = useState(0)const [flag, setFlag] = useState(false)// 根據(jù)布爾值進(jìn)行計(jì)算,動(dòng)態(tài)返回內(nèi)容const tips = () => {console.log('觸發(fā)了 tips 的重新計(jì)算')return flag ? <p>1</p> : <p>2</p>}return (<><h1>父組件</h1><p>count 的值是:{count}</p><p>flag 的值是:{String(flag)}</p>{tips()}<button onClick={() => setCount((prev) => prev + 1)}>+1</button><button onClick={() => setFlag((prev) => !prev)}>Toggle</button><hr /><Son num={count} /></>)
}
點(diǎn)擊父組件中的 +1 按鈕, 發(fā)現(xiàn) count 在自增, flag 不會(huì)改變, 此時(shí)也會(huì)觸發(fā) tips 函數(shù)的重新執(zhí)行, 造成性能的浪費(fèi)。
希望如果 flag 沒(méi)有發(fā)生變化, 則避免 tips 函數(shù)的重新計(jì)算, 從而優(yōu)化性能。此時(shí)需要用到 React Hooks 提供的 useMemo API。
useMemo()
const memoValue = useMemo(() => {return 計(jì)算得到的值
}, [value]) // 表示監(jiān)聽(tīng) value 的變化
- 第一個(gè)參數(shù)為函數(shù), 用于處理計(jì)算的邏輯, 必須用到得到的值。
- 第二個(gè)參數(shù)為數(shù)組, 為依賴項(xiàng), 依賴項(xiàng)發(fā)生變化, 觸發(fā)之前的第一個(gè)參數(shù)的函數(shù)的執(zhí)行。如果不傳的話, 每次更新都會(huì)重新計(jì)算。如果傳入的是[], 那么只會(huì)第一個(gè)render的時(shí)候觸發(fā), 以后不會(huì)重新渲染。
import React, { useEffect, useState, useMemo } from 'react'// 根據(jù)布爾值進(jìn)行計(jì)算,動(dòng)態(tài)返回內(nèi)容
const tips = useMemo(() => {console.log('觸發(fā)了 tips 的重新計(jì)算')return flag ? 1 : 2
}, [flag])
此時(shí)點(diǎn)擊+1, 不會(huì)觸發(fā)tips的重新計(jì)算。
3.useCallback
useMemo
能夠達(dá)到緩存某個(gè)變量值的效果, 而當(dāng)前要學(xué)習(xí)的 useCallback
用來(lái)對(duì)組件內(nèi)的函數(shù)進(jìn)行緩存, 返回的是函數(shù)。
useCallback 會(huì)返回一個(gè) memorized 回調(diào)函數(shù)供組件使用, 從而防止組件每次 rerender 時(shí)反復(fù)創(chuàng)建相同的函數(shù), 節(jié)省內(nèi)存, 提高性能。
-
第一個(gè)參數(shù)為一個(gè)函數(shù), 處理業(yè)務(wù)邏輯, 這個(gè)函數(shù)就是需要被緩存的函數(shù)
-
第二個(gè)參數(shù)為依賴項(xiàng)列表, 依賴項(xiàng)變化時(shí)才會(huì)重新執(zhí)行 useCallback。如果省略的話, 每次更新都會(huì)重新計(jì)算, 如果為[], 就只是第一次render的時(shí)候觸發(fā)一次。
當(dāng)輸入框觸發(fā) onChange 事件時(shí), 會(huì)給 kw 重新賦值。kw 值的改變會(huì)導(dǎo)致組件的 rerender, 組件的 rerender 會(huì)導(dǎo)致反復(fù)創(chuàng)建 onKwChange 函數(shù)并添加到 Set 集合, 造成了不必要的內(nèi)存浪費(fèi)。
import React, {useState} from 'react';// 用來(lái)存儲(chǔ)函數(shù)的 set 集合
const set = new Set()const Search:React.FC = () => {const [kw, setKw] = useState('')const onKwChange = (e: React.ChangeEvent<HTMLInputElement>) => {setKw(e.currentTarget.value)}// 把 onKwChange 函數(shù)的引用,存儲(chǔ)到 set 集合中set.add(onKwChange)// 打印 set 集合中元素的數(shù)量console.log('set 中函數(shù)的數(shù)量為:' + set.size)return (<><input type="text" value={kw} onChange={onKwChange} /><hr /><p>{kw}</p><p></p></>)
}export default Search;
每次文本框的值發(fā)生變化, 會(huì)打印set.size的值, 這個(gè)值一直在自增 +1, 因?yàn)槊看谓M件 rerender 都會(huì)創(chuàng)建一個(gè)新的 onKwChange 函數(shù)添加到 set 集合中。
為了防止 Search 組件 rerender 時(shí)每次都會(huì)重新創(chuàng)建 onKwChange 函數(shù), 可以使用 useCallback 對(duì)這個(gè)函數(shù)進(jìn)行緩存。
import React, {useCallback, useState} from 'react';// 用來(lái)存儲(chǔ)函數(shù)的 set 集合
const set = new Set()const Search:React.FC = () => {const [kw, setKw] = useState('')const onKwChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {setKw(e.currentTarget.value)}, [])// 把 onKwChange 函數(shù)的引用,存儲(chǔ)到 set 集合中set.add(onKwChange)// 打印 set 集合中元素的數(shù)量console.log('set 中函數(shù)的數(shù)量為:' + set.size)return (<><input type="text" value={kw} onChange={onKwChange} /><hr /><p>{kw}</p><p></p></>)
}export default Search;
無(wú)論 input 的值如何發(fā)生變化, 每次打印的 set.size 的值都是 1。證明我們使用 useCallback 實(shí)現(xiàn)了對(duì)函數(shù)的緩存。
4.useTransition
useTransition 可以將一個(gè)更新轉(zhuǎn)為低優(yōu)先級(jí)更新, 使其可以被打斷, 不阻塞 UI 對(duì)用戶操作的響應(yīng), 能夠提高用戶的使用體驗(yàn)。它常用于優(yōu)化視圖切換時(shí)的用戶體驗(yàn)。
Home、Movie、About 3個(gè)標(biāo)簽, 其中Movie 是一個(gè)很耗性能的組件, 在渲染 Movie 組件期間頁(yè)面的 UI 會(huì)被阻塞, 用戶會(huì)感覺(jué)頁(yè)面十分卡頓。
import React, { useState } from 'react'export const TabsContainer: React.FC = () => {// 被激活的標(biāo)簽頁(yè)的名字const [activeTab, setActiveTab] = useState('home')// 點(diǎn)擊按鈕,切換激活的標(biāo)簽頁(yè)const onClickHandler = (tabName: string) => {setActiveTab(tabName)}return (<div style={{ height: 500 }}><TabButton isActive={activeTab === 'home'} onClick={() => onClickHandler('home')}>首頁(yè)</TabButton><TabButton isActive={activeTab === 'movie'} onClick={() => onClickHandler('movie')}>電影</TabButton><TabButton isActive={activeTab === 'about'} onClick={() => onClickHandler('about')}>關(guān)于</TabButton><hr />{/* 根據(jù)被激活的標(biāo)簽名,渲染對(duì)應(yīng)的 tab 組件 */}{activeTab === 'home' && <HomeTab />}{activeTab === 'movie' && <MovieTab />}{activeTab === 'about' && <AboutTab />}</div>)
}// Button 組件 props 的 TS 類型
type TabButtonType = React.PropsWithChildren & { isActive: boolean; onClick: () => void }
// Button 組件
const TabButton: React.FC<TabButtonType> = (props) => {const onButtonClick = () => {props.onClick()}return (<button className={['btn', props.isActive && 'active'].join(' ')} onClick={onButtonClick}>{props.children}</button>)
}// Home 組件
const HomeTab: React.FC = () => {return <>HomeTab</>
}// Movie 組件
const MovieTab: React.FC = () => {const items = Array(100000).fill('MovieTab').map((item, i) => <p key={i}>{item}</p>)return items
}// About 組件
const AboutTab: React.FC = () => {return <>AboutTab</>
}
.btn {margin: 5px;background-color: rgb(8, 92, 238);color: #fff;transition: opacity 0.5s ease;
}.btn:hover {opacity: 0.6;transition: opacity 0.5s ease;
}.btn.active {background-color: rgb(3, 150, 0);
}
語(yǔ)法結(jié)構(gòu)
import { useTransition } from 'react';function TabContainer() {const [isPending, startTransition] = useTransition();// ……
}
- isPending 布爾值: 是否存在待處理的 transition, 如果值為 true, 說(shuō)明頁(yè)面上存在待渲染的部分, 可以給用戶一個(gè)加載的提示。
- startTransition 函數(shù): 調(diào)用此函數(shù), 可以將狀態(tài)的更新標(biāo)記為低優(yōu)先級(jí)的, 不阻塞UI 對(duì)用戶操作的響應(yīng)。
使用 useTransition 把點(diǎn)擊按鈕后為 activeTab 賦值的操作, 標(biāo)記為低優(yōu)先級(jí), UI 不被阻塞。
import React, { useState, useTransition } from 'react'export const TabsContainer: React.FC = () => {// 被激活的標(biāo)簽頁(yè)的名字const [activeTab, setActiveTab] = useState('home')const [, startTransition] = useTransition()// 點(diǎn)擊按鈕,切換激活的標(biāo)簽頁(yè)const onClickHandler = (tabName: string) => {startTransition(() => {setActiveTab(tabName)})}
}
點(diǎn)擊 Movie 按鈕后, 狀態(tài)的更新被標(biāo)記為低優(yōu)先級(jí), About 按鈕的 hover 效果和點(diǎn)擊操作都會(huì)被立即響應(yīng)。
5.useDeferredValue
useDeferredValue 提供一個(gè) state 的延遲版本, 根據(jù)其返回的延遲的 state 能夠推遲更新 UI 中的某一部分。
import { useState, useDeferredValue } from 'react';function SearchPage() {const [kw, setKw] = useState('');// 根據(jù) kw 得到延遲的 kwconst deferredKw = useDeferredValue(kw);// ...
}
SearchResult 組件會(huì)根據(jù)用戶輸入的關(guān)鍵字, 循環(huán)生成大量的p標(biāo)簽, 會(huì)浪費(fèi)性能。
import React, { useState } from 'react'// 父組件
export const SearchBox: React.FC = () => {const [kw, setKw] = useState('')const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {setKw(e.currentTarget.value)}return (<div style={{ height: 500 }}><input type="text" value={kw} onChange={onInputChange} /><hr /><SearchResult query={kw} /></div>)
}// 子組件,渲染列表項(xiàng)
const SearchResult: React.FC<{ query: string }> = (props) => {if (!props.query) returnconst items = Array(40000).fill(props.query).map((item, i) => <p key={i}>{item}</p>)return items
}
優(yōu)化1
優(yōu)化2
當(dāng) kw 的值頻繁更新時(shí), deferredKw 的值會(huì)明顯滯后, 此時(shí)用戶在頁(yè)面上看到的列表數(shù)據(jù)并不是最新的, 給內(nèi)容添加 opacity 透明度, 表明當(dāng)前看到的內(nèi)容已過(guò)時(shí)。