一、useMemo
1、语法与参数
            
            
              js
              
              
            
          
          const cacheValue = useMemo(calculateValue, dependencies);
        (1)calculateValue参数
calculateValue表示要计算缓存值的函数;
- 该函数是一个没有任何参数的纯函数,可以返回任意类型;
 - React会在首次渲染时调用该函数;在更新时,会根据dependencies是否变化决定是否需要调用该函数;
- 如果dependencies没有发生变化,React将直接返回相同值;
 - 如果dependencies发生变化,React会调用该函数计算最新结果并返回;
 
 
(2)dependencies参数
用于判断calculateValue是否更新的依赖项;
依赖项列表中的值包括:props、state、所有在组件内部直接声明的变量和函数;
2、作用
在每次重新渲染时缓存计算的结果,跳过组件的重新渲染;【默认情况下,当一个组件重新渲染时,React会递归地重新渲染它的所有子组件】
该hook可以作为性能优化的手段,可以将每次渲染都需要大量计算的内容缓存起来;
传入useMemo的函数在渲染期间执行;
useMemo不会让首次渲染更快,只是会帮助你跳过不必要的更新;
3、返回值
初次渲染:返回calculateValue函数调用返回的结果;
更新渲染:依赖没有变化,返回上一次渲染中缓存的结果;
依赖变化,返回这一次渲染传入的calculateValue函数调用返回的结果;
4、useMemo与useCallback的区别
- useCallback缓存的是传入的fn函数,不会调用该函数;
 - useMemo缓存的是函数的调用结果,在调用useMemo会直接调用传入的函数;
 
二、useMemo源码
1、mountMemo - 首次渲染
            
            
              js
              
              
            
          
          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; // 返回我们需要的结果 
}
        2、updateMemo - 更新渲染
            
            
              js
              
              
            
          
          function updateMemo<T>(
    nextCreate: () => T, 
    deps: Array<mixed> | void | null 
): T { 
    const hook = updateWorkInProgressHook(); 
    const nextDeps = deps === undefined ? null : deps; // 获取下一次更新时的新依赖 
    const prevState = hook.memoizedState; // 获取的其实是我们之前缓存的内容:[nextValue, nextDeps] 缓存的计算结果及其依赖 
    if (prevState !== null) { 
        if (nextDeps !== null) { 
            const prevDeps: Array<mixed> | null = prevState[1]; // 获取旧的依赖信息 
            if (areHookInputsEqual(nextDeps, prevDesps)) { 
                return prevState[0]; // 若依赖没有发生变化,则直接返回我们缓存的结果,不会调用传入的回调函数 
            } 
        } 
    } 
    // 不满足上述条件,则直接调用回调函数,重新计算结果 
    const nextValue = nextCreate(); 
    hook.memoizedState = [nextValue, nextDeps]; // 重新缓存结果及依赖 
    return nextValue; 
}
        【小结】
从源码分析,我们可以得出结论:在首次渲染时,传入的回调函数一定会执行一次;而在更新阶段,我们会对比新旧依赖,判断是否需要调用函数重新计算结果,若依赖没有发生变化,则直接返回我们缓存的结果,无需再次调用函数计算一次,从而可以跳过一些代价昂贵的重新计算。
三、useCallback
1、定义
useCallback是一个允许你在多次渲染中缓存函数的React Hook;在组件顶层调用;
2、语法及参数
            
            
              js
              
              
            
          
          const cacheFn = useCallback(fn, dependencies);
        (1)fn参数
fn表示想要缓存的函数;该函数可以接受任何参数并且返回任何值;
React不会调用该函数,而是在首次渲染时返回该函数;
在下一次渲染时,若dependencies没有发生变化,React将会返回相同的函数;
(2)dependencies参数
用于判断fn是否更新的依赖项;
依赖项列表中的值包括:props、state、所有在组件内部直接声明的变量和函数;
3、作用
允许你在多次渲染中缓存函数;
useCallback不会阻止创建函数;每次组件重新渲染时,还是会创建一个新的函数,但是如果依赖没有变化,那么React会忽略它并返回缓存的函数;
4、返回值
- 初次渲染:返回传入的函数fn;
 - 更新渲染:依赖没有变化,返回上一次渲染中缓存的fn函数;
依赖变化,返回这一次渲染传入的fn函数; 
5、useCallback的使用场景
- 将其作为props传递给包装在memo中的组件;
- 如果props没有更改,则希望跳过重新渲染;
 - 缓存允许组件仅在依赖项更改时重新渲染;
 
 - 传递的函数可能作为某些hook的依赖;
 - 建议将自定义hook返回的任何函数包裹在useCallback中;
 
【使用useMemo实现useCallback】
            
            
              js
              
              
            
          
          function useCallback(fn, deps) { 
    return useMemo(() => fn, deps); // 使用useMemo返回传入的fn 
}
        四、useCallback源码
1、mountCallback - 首次渲染
            
            
              js
              
              
            
          
          function mountCallback<T>( 
    callback: T, 
    deps: Array<mixed> | void | null 
): T { 
    const hook = mountWorkInProgressHook(); 
    const nextDeps = deps === undefined ? null : deps; 
    hook.memoizedState = [callback, nextDeps]; // 直接缓存传入的callback,没有调用 
    return callback; 
} 
        2、updateCallback - 更新渲染
            
            
              js
              
              
            
          
          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 (areHookInoutsEqual(nextDeps, prevDeps)) { 
                return prevState[0]; 
            } 
        } 
    } 
    hook.memoizedState = [callback, nextDeps]; 
    return callback; 
}
        【代码补充】判断新旧依赖是否相同
            
            
              js
              
              
            
          
          function areHookInputsEqual( 
    nextDeps: Array<mixed>, // 新的依赖 
    prevDeps: Array<mixed> | null, // 旧的依赖 
) { 
    if (prevDeps === null) {
        return false;
    }
    for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { 
        if (is(nextDeps[i], prevDeps[i])) { 
            continue; 
        } 
        // 不相同则返回false 
        return false; 
    } 
    return true; 
} 
// is函数 
function is(x: any, y: any) { 
    return ( 
        (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare 
    ); 
} 
const objectIs: (x: any, y: any) => boolean = 
    typeof Object.is === 'function' ? Object.is : is;