性能优化是一个很大的话题,咱们从 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 官方文档