React Hooks 学习笔记—— useMemo ? useRef ?

1,759 阅读2分钟

问题描述

在一个 function 组件中使用 echarts 生成图表。大概思路如下:

function Demo(){
  const [chartData, setChartData] = useState([1,2,3])
  useEffect(updateChart, [chartData])
  let option = {	
    xAxis:{
      data:[]
      }
      // ...
    }
  
  function updateChart(){
  	option.xAxis.data = chartData
    //...
  }
}

这时候 Linter 会提示需要把 option 加到 effect 的依赖列表里面。加进入之后呢?Linter 又会提示,对 option 变量的依赖会导致每一次渲染时都会触发 effect,建议使用 useMemo 缓存一下这个变量。

The 'option' object makes the dependencies of useEffect Hook change on every render. To fix this, wrap the initialization of 'option' in its own useMemo() Hook.

按照提示的话,会改成下面这样:

useEffect(updateChart, [chartData, option])	// 添加 option
let option = useMemo(()=>({
  xAxis:{
    data:[]
  }
  // ...
},[])

这样代码可以正常跑了。但问题是,传递给 useMemo 的函数的返回值是不变的,也就是不需要重新被计算的。这样真的 OK 吗?

什么时候用 useMemo?

useMemo 会保证在每次渲染时返回值的引用相等性,用于避免在每次渲染时都进行高开销的计算。

那么在使用的之前,先问自己三个问题:

  1. 传递给 useMemo 的函数真的是高开销的计算吗?
  2. 返回值的引用会变化吗?如果返回的是一个基础数据类型(string, number, boolean, null, undefined, symbol)的变量,那引用是永远不会改变的。
  3. 这个值是否需要被重新计算?比如useMemo(()=>[1,2,3],[]),显然是不需要的。

优化办法

其实这里只是需要保存一个变量,并且保证渲染之间引用相等。那完全可以用 useRef

useEffect(updateChart, [chartData])	// 不需要添加更多依赖
let optionRef = useRef({	// 把 option 存到 optionRef.current 中
  xAxis:{
    data:[]
  }
  // ...
})
function updateChart(){
  optionRef.current.xAxis.data = chartData	
  //...
}

useRef 用于创建一个可变的 ref 对象,这个对象在组件的整个生命周期内保持不变。ref 对象的 current 可以存任何值,不只是 Dom。参考阅读 FAQ中的这个问题。所以这种情形下,完全可以使用 useRef

从概念上讲,你可以认为 refs 就像是一个 class 的实例变量。

如果觉得每次都需要.current 很烦,只需要解构并且重命名:

const { current: option } = useRef([1,2,3])

参考阅读

You’re overusing useMemo: Rethinking Hooks memoization