React Hooks - React.memo、useMemo 、useCallback

187 阅读2分钟
  • React.memo:依赖的 props 变化,子组件重新 render ( 浅比较 )
  • useMemo:缓存一个值,依赖更新( 对照vue中的computed )
  • useCallback:缓存一个函数,依赖更新( 对照vue中的watch )

1、useMemo & memo

// 父组件
import React, { useMemo, useState } from 'react';
import Son from './Son';

const MemoCallBack = () => {
  const [count, setCount] = useState(0);
  const [sex, setSex] = useState('男');
  
  // 1、子组件不使用 memo 包裹
  //    父组件内任何值的变化,都会触发子组件重新 render
  // 2、子组件使用 memo 包裹
  //    只有子组件依赖的值发生变化的时候,子组件才会重新 render
  //    适当的配合 useMemo 使用
  
  // 子面量对象
  const arr = [
    { sex, age: 12 },
    { sex: '女', age: 18 },
    { sex: '男', age: 6 }
  ];
  // useState 对象
  const [arr, setArr] = useState([
    { sex, age: 12 },
    { sex: '女', age: 18 },
    { sex: '男', age: 6 }
  ]);
  // useMemo 对象
  const arr = useMemo(() => {
    return [
      { sex, age: 12 },
      { sex: '女', age: 18 },
      { sex: '男', age: 6 }
    ];
  }, [sex]);
  
  return (
    <div>
      <button onClick={() => {
        setCount(count + 1);
      }}>改变count</button>

      <button onClick={
        () => {
          setSex(sex === '男' ? '女' : '男');
        }}>改变性别</button>
    
      <button onClick={() => {
        setArr([...arr, { sex: '男', age: Date.now() }]);
      }}>新增人员</button>

      {/* 子组件 */}
      <Son arr={arr} />
    </div>
  );
};
export default MemoCallBack;



// 子组件
import React, {useEffect, memo} from 'react'
const Son = ({arr}) => {
  console.log('是否触发了子组件更新', arr)
  return (
    <>
      {arr.map(item => (
        <div key={item.age}>
          <span>性别:{item.sex} - </span>
          <span>年龄:{item.age}</span>
        </div>
      ))}
    </>
  )
}

export default memo(Son) 

2、useCallback

import React, {useMemo, useState} from 'react'
import Son from './Son'
const MemoCallBack = () => {
    const [count, setCount] = useState(0)
    const [sex, setSex] = useState('男')

    // 引用 普通 函数,父组件内任何值的变化,都会触发子组件重新 render
    // 引用 useCallBack 函数,只有依赖的值变化,才会触发子组件重新 render
 
    // 普通函数
    const changeCount1 = () => {
      setCount(count + 1);
    };

    // useCallBack函数
    const changeCount2 = useCallback(() => {
      setCount(count + 1);
    }, [sex]);

    return (
      <div>
        {/* 子组件 */}
        <Son {...{ changeCount1, changeCount2 }} />
      </div>
    );
}
export default MemoCallBack



// 子组件
import React, {useEffect} from 'react'
const Son = ({changeCount1,changeCount2}) => {
  console.log('是否触发了子组件更新', )
  return (
    <>
      <button onClick={changeCount1}>改变count</button>
      <button onClick={changeCount2}>改变Sex</button>
    </>
  )
}

export default Son

3、useMemo 源码

function mountMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

function updateMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      // 依赖没发生变化,返回旧值
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  // 依赖发生变化,重新计算
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

4、useCallback 源码

function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  hook.memoizedState = [callback, nextDeps];
  return callback;
}