Skip to content

useMemo & useCallback?

在 React 16.8 新增了 Hook 的特性,在不编写 Class 的情况下使用 state 以及其他的 React 特性。

都知道函数组件中是 没有生命周期构子 和 state 的。React 新增的 Hook 为其增加了生命周期的特性在此就不多加讨论了。

这次主要讲的是 useMemouseCallback,主要是作为性能优化的手段

useMemo

使用 function 的形式来声明组件(即函数组件),失去了shouldCompnentUpdate(在组件更新之前)这个生命周期,主要进行状态对比,如果需要则进行改变。

也就是说使用Hooks的 useEffect(替代生命周期)后我们没有办法通过组件更新前条件来决定组件是否更新。

而且在函数组件中,也不再区分 mountupdate 两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗,例如子组件里有异步请求,那每次执行都会请求一次。

useMemo 是作为一个具有暂存值能力的 Hook

jsx
// 格式,只有依赖数组中的依赖项改变才会重新计算新的值
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])

举例

有一个父组件和一个子组件,父组件有个点击累加器,父组件会传递一个 data 给子组件

jsx
import React, { memo, useState } from 'react'

const Child = memo(({ data, onChange }) => {
  console.log('child render...')
  return (
    <div>
      <div>child</div>
      <div>{data.name}</div>
      <input type="text" onChange={onChange} />
    </div>
  );
})

const Father = () => {
  console.log('Hook render...')
  const [count, setCount] = useState(0)
  const [name, setName] = useState('rose')
  const [text, setText] = useState('')

  const data = {
    name
  }

  return (
    <div>
      <div>count: {count}</div>
      <div>text : {text}</div>
      <button onClick={() => setCount(count + 1)}>count + 1</button>
      <Child data={data} />
    </div>
  )
}

export default Father

运行效果图

memo

分析

父组件和子组件都执行一遍,没有问题。当我点击 count+1 按钮后,count 的值改变,name 没有改变,即依赖 namedata 也没有改变,但实际子组件也重新执行了一边。

memo

🤔🤔🤔

因为,count 每次更新时,Hook 都会执行一遍,则 data 每次都会重新声明一遍,导致 data 的数据没有变,但地址变了,因此 child 组件会有不必要的更新。

jsx
// 将 data 改为
const data = useMemo(() => {
    return { name }
}, [name])

重复上例步骤,结果, child 组件并没有更新了,只有在 name 改变时 child 组件才更新一遍。这就是 useMemo 带来的性能优化效果。

useMemo

useCallback

useCallbackuseMemo 类似,都有着缓存的能力。而 useCallback 是缓存函数的。

jsx
// 格式
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useCallback(fn, deps) 
// 相当于
useMemo(() => fn, deps)

官方解析:把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

举例

jsx
import React, { memo, useCallback, useState, useMemo } from 'react'

const Child = memo(({ data, onChange }) => {
  console.log('child render...')
  return (
    <div>
      <div>child</div>
      <div>{data.name}</div>
      <input type="text" onChange={onChange} />
    </div>
  );
})

const Father = () => {
  console.log('Hook render...')
  const [count, setCount] = useState(0)
  const [name, setName] = useState('zhian')
  const [text, setText] = useState('')

  const data = useMemo(() => {
    return { name }
  }, [name])

  const onChange = () => {
    setText(e.target.value)
  }

  return (
    <div>
      <div>count: {count}</div>
      <div>text : {text}</div>
      <button onClick={() => setCount(count + 1)}>count + 1</button>
      <Child data={data} onChange={onChange} />
    </div>
  )
}

export default About

这个例子执行上面 useMemo 同样的操作,出现同样的问题,Child组件被更新了一遍,但实际值并没有改变。

导致的原因也是和上面的一样,地址改变了

jsx
// 将onChange改为一下就好了,原理原因也是一样
const onChange = useCallback((e) => {
   setText(e.target.value)
}, [])

这时候应该会提出一个疑问,为什么,值每修改一遍,组件都会执行一遍???

解析

在js中,当函数执行时,会创建一个被称为执行环境的对象,这个对象在每次函数执行时都是不同的,当多次执行该函数时会创建多个执行环境。这个执行环境会在函数执行完毕后销毁。所以每次 rerender 时都会创建新的执行环境,并为其内部的方法重新分配空间。

附:REACT.memo 优化

class 组件中可以使用 shoudComponentUpdatePureComponent 来做性能优化。 React为函数式组件提供了叫React.memo 一个高阶组件, 只适用于函数组件,而不适用 class 组件。

通过将组件包装在React.memo 中调用,通过这种记忆组件渲染结果的方式来提高组件的性能。这意味着当props没有变化时, React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

jsx
const MyComponent = React.memo(function (props) {
  // only renders if props have changed
})

默认情况下其只会对 props浅层对比,遇到层级比较深的复杂对象时,表示力不从心了。可以传入第二个参数进行深度比较。(比如对象,可以使用 lodash 的 _.isEqual(value, other) 进行深比较)

jsx
function arePropsEqual(prevProps, nextProps) {
  // your code
  return prevProps === nextProps;
}

export default memo(Button, arePropsEqual);
  1. 首先,memo 的用法是:函数组件里面的 PureComponent

但是,如果函数组件被 React.memo 包裹,且其实现中拥有 useState useContext 的 Hook,当 context 发生变化时,它仍会重新渲染。

  1. 而且,memo是浅比较,意思是,对象只比较内存地址,只要你内存地址没变,管你对象里面的值千变万化都不会触发render

Released under the MIT License.