性能優化是一個很大的話題,咱們從 React Function 組件的性能優化中發散一下思惟,精細化渲染指得是讓每一個渲染的粒度更細,讓該渲染的部分渲染,沒必要要渲染的部分緩存,javascript
React 從 一次 SetState
到界面更新大體通過這些步驟:html
調用 SetState(更新State)
=> render Function(組件render,函數執行)
=> diff(對比Vdom差別)
=> commit
=> render Dom(更新界面)
java
每次 render
並不必定會形成 頁面 UI
的更新,其中會通過 diff
的優化react
咱們主要說說如何減小沒必要要的 render Function
,減小沒必要要的組件函數吊用。數組
首先安裝 react devtools緩存
在 Components-setting-General 中打開 Highlight updates when components render.性能優化
這樣你就能看到哪些組件在 setState 後 render 了markdown
在 Components-setting-General Profiling 中打開 Record why each component rendered while profiling.dom
這樣你就能知道是什麼致使組件從新 render 了函數
咱們以一個常見的列表渲染爲例,咱們想經過點擊一個按鈕更新列表第一項的 num
咱們可能會寫出以下代碼
const listData = [
{ id: 'id-1', num: 1 },
{ id: 'id-2', num: 2 }
]
export const List = () => {
const [list, setList] = useState(listData)
const handleUpdateFirstItem = () => {
const newList = [...list]
newList[0] = { ...newList[0], num: Math.random() }
// newList[0].num = Math.random() // 這樣寫永遠都是是錯誤的,即便在這裏寫,最後頁面顯示結果也是正確的. react 不可變數據 原則瞭解一下
setList(newList)
}
return (
<ul> {list.map((item) => ( <li key={item.id}>Num : {item.num} {console.log(`renderItemId: ${item.id}`)}</li> ))} <button onClick={handleUpdateFirstItem}>修改第一項</button> </ul>
)
}
複製代碼
點擊按鈕,咱們能夠看到 renderItemId
的 id-1
id-2
都打印了,可是很明顯第二項是能夠不須要render的,那該怎麼作呢。
把 每一個 li
抽離成組件 Item
組件, 並memo
,memo
做用是和 React.PureComponent
同樣,只不過是用在函數組件中,會對 props
和 state
做 淺比較。若是未發生變化,組件則不會更新。
export const List = () => {
const [list, setList] = useState(listData)
const handleUpdateFirstItem = () => {
const newList = [...list]
newList[0] = { ...newList[0], num: Math.random() }
// newList[0].num = Math.random() // 若是這樣寫,子組件就不更新了,想一想爲何,因此說 react 不可變數據 原則繼續瞭解一下
setList(newList)
}
return (
<ul> {list.map((item) => ( <Item key={item.id} item={item}/> ))} <button onClick={handleUpdateFirstItem}>修改第一項</button> </ul>
)
}
const Item = React.memo(({ item }) => {
console.log('renderItemId: ' + item.id)
return (
<li> {item.num} </li>
)
})
複製代碼
點擊按鈕,咱們能夠看到
renderItemId
只有的 id-1
打印了,看到這裏,須要記住:函數組件的 memo
和 class 組件的 React.PureComponent
,是性能優化的好幫手。
咱們須要儘量的保證傳入每一個 Item
組件的 props
不會發生變化。例如:想知道當前 Item
是不是被選中,應該在 List
組件上作判斷,而不是在 Item
組件裏判斷。 Item
只有 isActive
props, 而不是把 整個 activeIdList
傳入每一個 Item
跟其 id
作比較,由於 activeIdList
prop 的更新會致使每一個 Item
都會 render,而 props
只接收isActive
,只會在值真正變化的時候render Item
.
仍是常見的需求,咱們在上面列表的基礎上,想點擊某一項就更新某一項的 num
咱們可能會有這些方式去實現:
list
傳入每一個 Item
(極其不推薦)export const List = () => {
const [list, setList] = useState(listData)
return (
<ul> {list.map((item) => ( <Item setList={setList} list={list} key={item.id} item={item}/> ))} </ul>
)
}
const Item = React.memo(({ item, setList, list }) => {
const handleClick = () => {
const newList = [...list]
const index = newList.findIndex((s) => s.id === item.id)
newList[index] = { ...newList[index], num: Math.random() }
setList(newList)
}
console.log('renderItemId: ' + item.id)
return (
<li> {item.num} <button onClick={handleClick}>點擊</button> </li>
)
})
複製代碼
爲啥極其不推薦?咱們發現其實僅只須要從新
render
當前項,可是其餘 Item
也會更新。
經過 react devtools
咱們能夠看到每一項 Item
的 props 中的 list
致使從新 render
useCallback
緩存函數 沒法緩存組件export const List = () => {
const [list, setList] = useState(listData)
const handleChange = useCallback((id) => {
const newList = [...list]
const index = newList.findIndex((item) => item.id === id)
newList[index] = { ...newList[index], num: Math.random() }
setList(newList)
}, [list])
return (
<ul> {list.map((item) => ( <Item setList={setList} onClick={handleChange} key={item.id} item={item}/> ))} </ul>
)
}
const Item = React.memo(({ item, onClick }) => {
const handleClick = useCallback(() => {
onClick(item.id)
}, [item.id, onClick])
console.log('renderItemId: ' + item.id)
return (
<li> {item.num} <button onClick={handleClick}>點擊</button> </li>
)
})
複製代碼
這樣兩個
Item
仍是都從新 render
了,從分析工具中看到 props
中的 onClick
函數change了,由於 handleChange
即便 使用了 useCallback
緩存,可是因爲必須依賴 list
可是每次都會從新 setList
致使每次傳入的 handleChange
也是新建立的,破壞了meno
的效果。
方式2就是因爲 handleChange
依賴了 list
,致使函數每次都會建立,咱們想辦法用 ref
緩存一下。
export const List = () => {
const [list, setList] = useState(listData)
// 用 ref 緩存 list
const ref = useRef(list)
// 監聽 list 變化存到ref
useEffect(() => {
ref.current = list
}, [ref, list])
const handleChange = useCallback((id) => {
const newList = [...ref.current]
const index = newList.findIndex((item) => item.id === id)
newList[index] = { ...newList[index], num: Math.random() }
setList(newList)
}, [ref]) // deps 依賴ref 而不依賴 list
return (
<ul> {list.map((item) => ( <Item setList={setList} onClick={handleChange} key={item.id} item={item}/> ))} </ul>
)
}
const Item = React.memo(({ item, onClick }) => {
...
})
複製代碼
這樣就能夠實現點擊哪一項就只
render
哪一項。可是這樣寫每次須要c實在有點麻煩。
方式3 用起來有點麻煩,能夠自定義一個 useEventCallBack
hook, React 官方有給出,本身寫一個也行,這樣就簡單多了。
export const List = () => {
// ...
const handleChange = useEventCallBack((id) => {
...
},[list])
return (
// ...
)
}
複製代碼
useReducer
+ useContext
(多層數據傳遞推薦方式)該方法適用於多層級的組件結構,暫很少說。
總的來講就一句話,儘量讓只須要從新渲染的組件從新渲染。
回到本文的情景就是:
通常狀況下:儘可能讓每一個組件拆得粒度更細,讓組件 memo
緩存。讓組件的 props
儘量的不變化。可是某些場景 必定最形成組件 render
的情景下,反覆的 memo
淺比價也會產生開銷,因此具體狀況須要根據業務場景來作處理。
手動優化時:手動優化通常都是根據具體業務場景,去比較 props
,有時候須要比較的 props
較多能夠用 lodash 的 pick
,omit
等方法取須要比較等字段,而後用 isEqual
進行值的比較。 須要注意到,這些取值,和比較計算也會有開銷,因此仍是須要根據實際業務場景進行取捨權衡
Optimizing Performance React 官方文檔