setState
React 从 一次 SetState
到界面更新大致经过这些步骤:
调用 SetState(更新State)
=> render Function(组件render,函数执行)
=> diff(对比Vdom差异)
=> commit
=> render Dom(更新界面)
每次 render
并不一定会造成 页面 UI
的更新,其中会经过 diff
的优化
我们主要说说如何减少不必要的 render Function
,减少不必要的组件函数吊用。
欲善其事必利其器
-
首先安装 react devtools
-
在 Components-setting-General 中打开 Highlight updates when components render.
这样你就能看到哪些组件在 setState 后 render 了
-
在 Components-setting-General Profiling 中打开 Record why each component rendered while profiling.
这样你就能知道是什么导致组件重新 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的,那该怎么做呢。
精细化列表渲染 + memo 缓存组件
把 每个 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
.
有 Event 传递 如何优化
还是常见的需求,我们在上面列表的基础上,想点击某一项就更新某一项的 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
的效果。
方式三:改进方式二缓存 list
方式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实在有点麻烦。
方式四:useEventCallBack (推荐方式)
方式3 用起来有点麻烦,可以自定义一个 useEventCallBack
hook, React 官方有给出,自己写一个也行,这样就简单多了。
export const List = () => {
// ...
const handleChange = useEventCallBack((id) => {
...
},[list])
return (
// ...
)
}
方式五:利用 memo
or shouldComponentUpdate
手动优化 (推荐方式)
这里实际的业务情景是,只有 num
发生变化的时候才需要 render
,所以利用 memo
函数的第二个参数,只比较 props.num
就足够了。
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>
)
}
// 1. Function组件写法,用 memo 第二个参数,如果相等返回 true 则表示 不需要 `render`
const Item = React.memo(({ item, onClick }) => {
const handleClick = useCallback(() => {
onClick(item.id)
}, [item.id, onClick])
return <li>
{item.num}
<button onClick={handleClick}>点击</button>
</li>
}, (preProps, nextProps) => {
return preProps.item.num === nextProps.item.num
})
// 2. Class 组件,在 shouldComponentUpdate 生命周期函数上,对比 props,如果相等返回 true 则表示 需要 `render`
shouldComponentUpdate(nextProps, nextState) {
return nextProps.item.num !== this.props.item.num
}
总结
总的来说就一句话,尽可能让只需要重新渲染的组件重新渲染。
回到本文的情景就是:
一般情况下:尽量让每个组件拆得粒度更细,让组件 memo
缓存。让组件的 props
尽可能的不变化。但是某些场景 一定最造成组件 render
的情景下,反复的 memo
浅比价也会产生开销,所以具体情况需要根据业务场景来做处理。
手动优化时:手动优化一般都是根据具体业务场景,去比较 props
,有时候需要比较的 props
较多可以用 lodash 的 pick
,omit
等方法取需要比较等字段,然后用 isEqual
进行值的比较。 需要注意到,这些取值,和比较计算也会有开销,所以还是需要根据实际业务场景进行取舍权衡
参考文档
Optimizing Performance React 官方文档
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!