usecallback

549 阅读3分钟

首先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