首先useCallback作用: 官方文档:
Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.
简单来说就是返回一个函数,只有依赖项发生改变时才会返回一个新的函数。 useCallback的应用:
import react, {useState, useCallback} from 'react'
import Button from './Button'
export const App = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const [count3, sxtCount3] = useState(0);
const handelClick = () => {
setCount(count1 + 1);
}
const handelClick2 = useCallback(() => {
setCount(count2 + 1);
}, [count2]);
cosnt handelClick3 = () => {
setCount3(count3 + 1);
}
return (
<div>
<div>
<Button onClickButton={handelClick}>
button1
<span>Math.random()</span>
</Button>
</div>
<div>
<Buttons onClickButton={handelClick2}>button2</Buttons>
</div>
<div>
<Buttons onClickButton={handelClick3}>button3</Buttons>
</div>
</div>
)
};
const Buttons = ({onClickButton, children}) => {
return(
<div>
<button onClick = {onClickButton}>{children}</button>
<span>{Math.random()}</span> // 这里取得随机数,是为了每次更新都会体现不同的数
</div>
)
}
export React.mome(Buttons); // React.memo包裹的组件,会对props做一个浅层比较。如果props没有发生改变,则不会重新渲染组件
运行代码后点击按钮后发现:
点击button1时,button1和button3后面的数都会变
点击button2时,button2会将3个按钮后的内容都更新
点击button3时,button1和button3后面的数都会变
《------原因------》
"因为父组件刷新一定会导致子组件刷"
React.memo对包裹的组件的props进行浅层比较,
因为button2传递的函数是被useCallback包裹的,所以只有在依赖项发生改变时才会更新button2的函数,
才会导致button2后面的数字进行更新,
而button3传递的是没有被useCallback包裹的函数,父组件刷新函数会重新创建,
React.memo检测到是新创建的函数并不是原来的函数会对组件进行刷新
button1每次父组件刷新都会引起子组件的刷新所以后面的数字会发生改变
button3因为每次父组件刷新都会重新创建函数变量,即便是相同的函数但是新函数不等于老函数,所以React.mome对比后重新刷新了组件。
但是button2点击事件触发的函数是useCallback返回的函数,只有在count2发生改变时,useCallback才会返回新的函数,才会被React.mome检测到导致组件的更新。(因为useCallback返回新的函数,函数内部作用域也会随之更新,所以获取道到的都是最新的count)
但是如果useCallback后面不设置依赖项,就意味着这个方法没有依赖值,不会被更新。并且由于js的静态作用域,函数不更新count2一直等于0,意味着每次都是 0 + 1,所以就导致Button组件只会以为count2从0 -> 1 而更新一次
上面的问题是不更新造成的问题,现在看看频繁更新造成的问题
const [text, setText] = useState('');
const handelClick = useCallback(() => {
//******
}, [text]);
return (
<form>
<input value={text} onChange ={ e => setText(e.target.value) }/>
<otherForm onSubmit={handelClick}/>
</form>
)
这种情况下,根据input的输入情况,text的变化指定非常频繁,加入otherForm是一个非常大的组件,这里代码的效能就会下降,优化方案:可以使用useRef来帮忙
const [text, setText] = useState('');
const helpRef = useRef();
const handelClick = useCallback(() => {
console.log(helpRef.current);
}, [helpRef]);
return (
<div>
<input value={text} onChange={ e => {
setText(e.target.value);
helpRef.current = e.target.value;
}}/>
<otherForm onSubmit={handelClick}/>
</div>
)
使用useRef可以生成一个变量让其在组件每个生命周期都能访问到,并且handelClick并不会因为text的更新而更新,也就不会让otherForm多次渲染。
那是否可以将所有的方法都包裹一层useCallback呢?答案:不要把所有的方法都包裹上useCallback
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const handleClickButton1 = () => {
setCount1(count1 + 1)
};
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1)
}, [count2]);
return (
<>
<button onClick={handleClickButton1}>button1</button>
<button onClick={handleClickButton2}>button2</button>
</> )
上面这种写法,点击button是会会触发组件的一定会触发当前组件的重新渲染,所以useCallback就起不到优化作用了,而且重新渲染组件会传给useCallback一个新的函数,useCallback还会储存一个老的函数,通过对比count2是否发生改变,决定返回新的函数还是老的函数,这样就产生了不必要的消耗,所以不能盲目的使用useCallback,
要配合子组件的shouldComponentUpdata或者React.memo一起来使用。
作者:冲破冰的花
借鉴了文章: # 详解 React useCallback & useMemo