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;
相关推荐
光影少年1 小时前
usemeno和usecallback区别及使用场景
react.js
吕彬-前端6 小时前
使用vite+react+ts+Ant Design开发后台管理项目(二)
前端·react.js·前端框架
小白小白从不日白6 小时前
react hooks--useCallback
前端·react.js·前端框架
恩婧6 小时前
React项目中使用发布订阅模式
前端·react.js·前端框架·发布订阅模式
程序员小杨v17 小时前
如何使用 React Compiler – 完整指南
前端·react.js
谢尔登8 小时前
Babel
前端·react.js·node.js
卸任8 小时前
使用高阶组件封装路由拦截逻辑
前端·react.js
清汤饺子11 小时前
实践指南之网页转PDF
前端·javascript·react.js
霸气小男11 小时前
react + antDesign封装图片预览组件(支持多张图片)
前端·react.js
小白小白从不日白12 小时前
react 组件通讯
前端·react.js