每天对自己多问几个为什么,总是有着想象不到的收获。 一个菜鸟小白的成长之路(copyer)
前言
在前面的项目中敲代码,知道可以用useCallback
和 useMemo
,但是感觉就是不写,怎么说呢?它们是从哪里进行性能优化的呢?
以前的, 我的思维陷入了一个误区, 认为 useCallback
是对函数的性能优化。其实并不然。
useCallback
的性能优化在于: 是否让子组件进行重新渲染
上面的都是一些废话,可以不用看;当然最后一句话,还是可以看看,因为最后的真相就是它。
官网上useCallback
的基本用法和解释
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
返回一个 memoized 回调函数。
把内联回调函数及依赖项数组作为参数传入 useCallback
,它将返回该回调函数的 memoized
版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子组件时,它将非常有用。
useCallback
的闭包特性
例子:万能的计数器
代码:
import React, { useState, useCallback } from 'react'
const App = () => {
const [count, setCount] = useState(0)
const add1 = () => {
setCount(count + 1)
}
const add2 = useCallback(() => {
setCount(count + 1)
}, [])
return (
<div>
<h1>count的值:{count}</h1>
<button onClick={add1}>普通: +1</button>
<button onClick={add2}>useCallback:+1</button>
</div>
)
}
export default App;
截图流程:
为什么呢?
当我们点击普通按钮
的时候,把count
的值变为 4
;那为什么当点击useCallback
按钮的时候,count 的值 变为 1
,这里就涉及到useCallback
的闭包特性
useCallback
的第一个参数为一个函数,该函数里面使用了外部的变量 count
,然后返回一个 新的函数,满足的闭包的条件。
闭包的三个条件:
- 函数嵌套
- 调用外部函数的变量
- return
add2
在函数创建的过程中,就使用了count ,就记录下了当前count的值为0,所以当点击按钮的时候,就是在最初始的状态上加 1
这就是useCallback的闭包特性。
useCallback
的错误性能优化
代码示例:
const App = () => {
const [count, setCount] = useState(0)
const add1 = () => {
setCount(count + 1)
}
const add2 = useCallback(() => {
setCount(count + 1)
}, [count])
return (
<div style={{padding: '40px'}}>
<h1>count的值:{count}</h1>
<button onClick={add1}>普通: +1</button>
<button onClick={add2}>useCallback:+1</button>
</div>
)
}
add1
是一个普通的函数,
add2
是 useCallback 函数返回的memoized
函数
当我们改变count
的值,整个App组件都会重新刷新执行,所有的函数都会重新创建
// add1 的创建过程
const add1 = () => {
setCount(count + 1)
}
// add2 的创建过程
const add1 = () => {
setCount(count + 1)
}
const add2 = useCallback(add1, [count])
从上面的对比出来看, 创建add2
的过程,明显要比 add1
多一步,换句话说,add2
的创建更加的消耗性能。
上面的场景就说明, useCallback
并不是对函数进行性能优化的,反而会更加的消耗性能
useCallback
的正确使用场景
当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子组件时,它将非常有用。
上面就是官网的原话: 就是对子组件的重新渲染进行优化
// 基本代码
import React, { useState, useCallback } from 'react'
const OwnButton = (props) => {
const { title, add, children } = props
console.log('子组件渲染:', title);
return <button onClick={add}> { children} </button>
}
const App = () => {
console.log('父组件被渲染');
const [count, setCount] = useState(0)
const [other, setOther] = useState(false)
const add1 = () => {
setCount(count + 1)
}
const add2 = useCallback(() => {
setCount(count + 1)
}, [count])
return (
<div style={{padding: '40px'}}>
<h1>count的值:{count}</h1>
<OwnButton title="add1" add={add1}>普通:+1</OwnButton>
<OwnButton title="add2" add={add2}>useCallback:+1</OwnButton>
<button onClick={() => setOther(true)}>修改其他state的值</button>
</div>
)
}
export default App;
当父组件的状态改变的时候,子组件肯定会重新渲染。
初始状态:
父组件跟子组件都执行了。
点击按钮,是count发生改变:
这个是我们想要的现象,当count发生改变的时候,子组件也被重新渲染。
点击按钮,修改other的值
这就不是,我们想要的现象了,我们只是修改other的值,也没对OwnButton
组件产生影响,也被重新渲染了,所以就消耗性能了。
所以:总结一点, useCallback
要和memo
结合使用,才能有性能优化。
改造代码:
const OwnButton = memo((props) => {
const { title, add, children } = props
console.log('子组件渲染:', title);
return <button onClick={add}> { children} </button>
})
为什么结合memo后,组件就不会重新渲染了呢?
memo会对props进行浅层的比较,如果传递过来的props是相同的话,就不会进行重新渲染。恰好,useCallback
满足这一点,只要useCallback
的依赖条件没有改变,返回的就是同一函数(memoized
),所以就不会重新渲染。
点击修改other的值
看,想要的效果就回来啦。
总结
以前对useCallback
有误解,useCallback
大哥我错了,现在对你有一定的了解,还是很开心哟。
常用场景: 就是父组件传递个子组件函数,用useCallback
,再结合memo的使用,就nice了。
现在是凌晨1点钟了,昨天是中秋节,祝大家中秋节快乐。。。