react Hook之useMemo、useCallback及memo

注意:hooks只能在函數(無狀態組件)中使用html

useMome、useCallback、useEffect用法都差很少,都會在第一次渲染的時候執行,以後會在其依賴的變量發生改變時再次執行,而且這兩個hooks都返回緩存的值,useMemo返回緩存的變量,useCallback返回緩存的函數。react

const value = useMemo(fnM, [a]);

const fnA = useCallback(fnB, [a]);
複製代碼

一、memo的應用

React.memo 爲高階組件。它與React.PureComponent很是類似,但它適用於函數組件,但不適用於 class 組件。api

默認狀況下其只會對複雜對象作淺層對比,若是你想要控制對比過程,那麼請將自定義的比較函數經過第二個參數傳入來實現。這與shouldComponentUpdate 方法的返回值相反。數組

function MyComponent(props) {
  /* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
  /*
  若是把 nextProps 傳入 render 方法的返回結果與
  將 prevProps 傳入 render 方法的返回結果一致則返回 true,
  不然返回 false
  */
}
export default React.memo(MyComponent, areEqual);
複製代碼

當父組件引入子組件的狀況下,每每會形成組件之間的一些沒必要要的浪費,下面咱們經過例子來了解一下場景緩存

const Child = (props) => {
    console.log('子組件?')
    return(
        <div>我是一個子組件</div>
    );
}
const Page = (props) => {
    const [count, setCount] = useState(0);
    return (
        <>
            <button onClick={(e) => { setCount(count+1) }}>加1</button>
            <p>count:{count}</p>
            <Child />
        </>
    )
}
複製代碼

每次父組件更新count,子組件都會更新。以下版本使用memo,count變化子組件沒有更新

const Child = (props) => {
    console.log('子組件?')
    return(
        <div>我是一個子組件</div>
    );
}
const ChildMemo = memo(Child);

const Page = (props) => {
    const [count, setCount] = useState(0);

    return (
        <>
            <button onClick={(e) => { setCount(count+1) }}>加1</button>
            <p>count:{count}</p>
            <ChildMemo />
        </>
    )
}
複製代碼

二、使用useCallback

當父組件傳遞狀態給子組件的時候,memo好像沒什麼效果,子組件仍是執行了,這時候咱們就要引入hooks的useCallback、useMemo這兩個鉤子了。bash

//子組件會有沒必要要渲染的例子
interface ChildProps {
    name: string;
    onClick: Function;
}
const Child = ({ name, onClick}: ChildProps): JSX.Element => {
    console.log('子組件?')
    return(
        <>
            <div>我是一個子組件,父級傳過來的數據:{name}</div>
            <button onClick={onClick.bind(null, '新的子組件name')}>改變name</button>
        </>
    );
}
const ChildMemo = memo(Child);

const Page = (props) => {
    const [count, setCount] = useState(0);
    const [name, setName] = useState('Child組件');

    return (
        <>
            <button onClick={(e) => { setCount(count+1) }}>加1</button>
            <p>count:{count}</p>
            <ChildMemo name={name} onClick={(newName: string) => setName(newName)}/>
        </>
    )
}
複製代碼

在上面代碼基礎上,父級調用子級時,在onClick參數上加上useCallback,參數爲[],則第一次初始化結束後,不在改變。函數

//子組件沒有必要渲染的例子
interface ChildProps {
    name: string;
    onClick: Function;
}
const Child = ({ name, onClick}: ChildProps): JSX.Element => {
    console.log('子組件?')
    return(
        <>
            <div>我是一個子組件,父級傳過來的數據:{name}</div>
            <button onClick={onClick.bind(null, '新的子組件name')}>改變name</button>
        </>
    );
}
const ChildMemo = memo(Child);

const Page = (props) => {
    const [count, setCount] = useState(0);
    const [name, setName] = useState('Child組件');
    
    return (
        <>
            <button onClick={(e) => { setCount(count+1) }}>加1</button>
            <p>count:{count}</p>
            <ChildMemo name={name} onClick={ useCallback((newName: string) => setName(newName), []) }/>
            {/* useCallback((newName: string) => setName(newName),[]) */}
            {/* 這裏使用了useCallback優化了傳遞給子組件的函數,只初始化一次這個函數,下次不產生新的函數
        </>
    )
}
複製代碼

三、使用useMemo

//子組件會有沒必要要渲染的例子
interface ChildProps {
    name: { name: string; color: string };
    onClick: Function;
}
const Child = ({ name, onClick}: ChildProps): JSX.Element => {
    console.log('子組件?')
    return(
        <>
            <div style={{ color: name.color }}>我是一個子組件,父級傳過來的數據:{name.name}</div>
            <button onClick={onClick.bind(null, '新的子組件name')}>改變name</button>
        </>
    );
}
const ChildMemo = memo(Child);

const Page = (props) => {
    const [count, setCount] = useState(0);
    const [name, setName] = useState('Child組件');
    
    return (
        <>
            <button onClick={(e) => { setCount(count+1) }}>加1</button>
            <p>count:{count}</p>
            <ChildMemo 
                name={{ name, color: name.indexOf('name') !== -1 ? 'red' : 'green'}} 
                onClick={ useCallback((newName: string) => setName(newName), []) }
            />
        </>
    )
}
複製代碼

更新屬性name爲對象類型,這時子組件仍是同樣的執行了,在父組件更新其它狀態的狀況下,子組件的name對象屬性會一直髮生從新渲染改變,從而致使一直執行,這也是沒必要要的性能浪費。性能

解決這個問題,使用name參數使用useMemo,依賴於State.name數據的變化進行更新優化

interface ChildProps {
    name: { name: string; color: string };
    onClick: Function;
}
const Child = ({ name, onClick}: ChildProps): JSX.Element => {
    console.log('子組件?')
    return(
        <>
            <div style={{ color: name.color }}>我是一個子組件,父級傳過來的數據:{name.name}</div>
            <button onClick={onClick.bind(null, '新的子組件name')}>改變name</button>
        </>
    );
}
const ChildMemo = memo(Child);

const Page = (props) => {
    const [count, setCount] = useState(0);
    const [name, setName] = useState('Child組件');
    
    return (
        <>
            <button onClick={(e) => { setCount(count+1) }}>加1</button>
            <p>count:{count}</p>
            <ChildMemo 
                //使用useMemo,返回一個和本來同樣的對象,第二個參數是依賴性,當name發生改變的時候,才產生一個新的對象
                name={
                    useMemo(()=>({ 
                        name, 
                        color: name.indexOf('name') !== -1 ? 'red' : 'green'
                    }), [name])
                } 
                onClick={ useCallback((newName: string) => setName(newName), []) }
                {/* useCallback((newName: string) => setName(newName),[]) */}
                {/* 這裏使用了useCallback優化了傳遞給子組件的函數,只初始化一次這個函數,下次不產生新的函數
            />
        </>
    )
}
複製代碼

四、總結

在子組件不須要父組件的值和函數的狀況下,只須要使用memo函數包裹子組件便可。而在使用值和函數的狀況,須要考慮有沒有函數傳遞給子組件使用useCallback,值有沒有所依賴的依賴項而使用useMemo,而不是盲目使用這些hooks等。ui

參考連接: zh-hans.reactjs.org/docs/react-…

blog.csdn.net/weixin_4390…

相關文章
相關標籤/搜索