React源码之useMemo与useCallback

一、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;
相关推荐
星辰引路-Lefan7 小时前
深入理解React Hooks的原理与实践
前端·javascript·react.js
飞鸟malred8 小时前
vite+tailwind封装组件库
前端·react.js·npm
TE-茶叶蛋8 小时前
Vue Fragment vs React Fragment
javascript·vue.js·react.js
xd0000212 小时前
12.vite,webpack构建工具
react.js
WildBlue13 小时前
🚀 React初体验:从“秃头程序员”到“数据魔法师”的奇幻漂流
前端·react.js
哼唧唧_13 小时前
使用 React Native 开发鸿蒙(HarmonyOS)运动健康类应用的系统化准备工作
react native·react.js·harmonyos·harmony os5·运动健康
_一两风15 小时前
React 组件化开发:从项目创建到组件通信
react.js
工呈士15 小时前
Context API 应用与局限性
前端·react.js·面试
钟看不钟用15 小时前
React(1)——渲染完整流程
react.js
胡gh15 小时前
深入理解React,了解React组件化,脱离”切图崽“,迈向高级前端开发师行列
前端·react.js