React useCallback 和 useMemo

优化 React 应用的一个关键点就是防止不必要的组件渲染,如果你的 DOM 结构是A->B->C 这样的结构,那当你在操作每一层级的组件时需要考虑的是如何阻止渲染扩散到下一层级,重新渲染是否会是造成额外的资源消耗,比如多余的HTTP 请求,重新渲染复杂表格等等。

React useCallback 和 useMemo
Photo by Lautaro Andreani / Unsplash

自从换了新工作后已经有很久没有继续写前端的代码,最近刚好接了一个朋友的外包项目顺便重新学习了一下 React 的文档。

优化 React 应用的一个关键点就是防止不必要的组件渲染,如果你的 DOM 结构是A->B->C 这样的结构,那当你在操作每一层级的组件时需要考虑的是如何阻止渲染扩散到下一层级,重新渲染是否会是造成额外的资源消耗,比如多余的HTTP 请求,重新渲染复杂表格等等。

比如我们在使用 memo 来保证只有props 变化的时候再渲染,可能会忽略组件里定义的对象(object或者 function)都会在每次渲染时都会被创建为一个新的对象,例如:

const CompA = ({action, data}) => (<>{data.title}</>)
const MemoCompA = memo(CompA)

function Main() {
  const fn = () => {} 
  const data = {
    title: "title"
  }
  return (
   <>
     <MemoCompA action={fn} data={data}/>
   </>
  )
}

在上面的例子里:

  • Main 的每一次渲染中都会重新创建一个新的() => {}函数然后赋值给 fn
  • Main 的每一次渲染中都会重新创建一个新的 () => {title: "title"} 函数然后赋值给 data

此时对于 ComA 组建来说,在进行判断是否要重新渲染时都会得到:

(() => {} === () => {} && {title: "title"} === {title: "title"}) // false

这时候我们就需要引入 useCallback(fn, dependencies)useMemo(fn, dependencies)

function Main() {
  const fn = useCallback(() => {}, []) 
  const data = useMemo({
    title: "title"
  }, [])
  return (
   <>
     <CompA action={fn} data={data}/>
   </>
  )
}

useCallback(fn, dependencies)useMemo(fn, dependencies) 会将你的函数和值缓存到内存里,然后每次返回同一份数据。一个简单的实现如下:

let cache = null
let cacheDeps = []
const useMemo = (fn, deps) => {
  if (same(deps, cacheDeps) && cache) {
    return cache
  }
  cacheDeps = deps
  return cache = fn()
}
const useCallback = (fn, deps) => {
  return useMemo(() => fn, deps)
}

这时将我们的组件进行简单修改:

const CompA = ({action, data}) => (<>{data.title}</>)
const MemoCompA = memo(CompA)

function Main() {
  const fn = useCallback(() => {}, [])
  const data = useMemo(() => {
    title: "title"
  }, [])
  return (
   <>
     <MemoCompA action={fn} data={data}/>
   </>
  )
}

这时组件 CompA 在 Main 组建再次渲染的时候也不会再次渲染。


另外一种方法就是将数据和定义的函数定义在组件外:

const CompA = ({action, data}) => (<>{data.title}</>)
const MemoCompA = memo(CompA)
const fn = () => {}
const data = { title: "title" }
function Main() {
  return (
   <>
     <MemoCompA action={fn} data={data}/>
   </>
  )
}