那些年使用react的那些坑React

useCallback()、useMemo() 解决了什么问题?

2019-12-25  本文已影响0人  dkvirus

在阅读本文之前,请确保您具有 js 基础知识,知悉基础数据类型与复杂数据类型的区别。
如果下面的代码您不能理解,请略过此文以节约您的时间。

true === true       // true
false === false     // true
1 === 1             // true
'a' === 'a'         // true

{} === {}                 // false
[] === []                 // false
() => {} === () => {}     // false

目录:

React.memo()

问题

React 中当组件的 props 或 state 变化时,会重新渲染视图,实际开发会遇到不必要的渲染场景。看个例子:

子组件:

function ChildComp () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
}

父组件:

function ParentComp () {
  const [ count, setCount ] = useState(0)
  const increment = () => setCount(count + 1)

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp />
    </div>
  );
}

子组件中有条 console 语句,每当子组件被渲染时,都会在控制台看到一条打印信息。

点击父组件中按钮,会修改 count 变量的值,进而导致父组件重新渲染,此时子组件压根没有任何变化(props、state),但在控制台中仍然看到子组件被渲染的打印信息。

图1

我们期待的结果:子组件的 props 和 state 没有变化时,即便父组件渲染,也不要渲染子组件。

解决

修改子组件,用 React.memo() 包一层。

这种写法是 React 的高阶组件写法,将组件作为函数(memo)的参数,函数的返回值(ChildComp)是一个新的组件。

import React, { memo } from 'react'

const ChildComp = memo(function () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
})

觉得上面👆那种写法别扭的,可以拆开写。

import React, { memo } from 'react'

let ChildComp = function () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
}

ChildComp = memo(ChildComp)

此时再次点击按钮,可以看到控制台没有打印子组件被渲染的信息了。

(控制台中打印的那一行值是第一次渲染父组件时,渲染子组件打印的,后面再点击按钮重新渲染父组件时,并没有再重新渲染子组件)

图2

React.useCallback()

问题

可别以为到这里就结束了!

上面的例子中,父组件只是简单调用子组件,并未给子组件传递任何属性。
看一个父组件给子组件传递属性的例子:

子组件:(子组件仍然用 React.memo() 包裹一层)

import React, { memo } from 'react'

const ChildComp = memo(function ({ name, onClick }) {
  console.log('render child-comp ...')
  return <>
    <div>Child Comp ... {name}</div>
    <button onClick={() => onClick('hello')}>改变 name 值</button>
  </>
})

父组件:

function ParentComp () {
  const [ count, setCount ] = useState(0)
  const increment = () => setCount(count + 1)

  const [ name, setName ] = useState('hi~')
  const changeName = (newName) => setName(newName)  // 父组件渲染时会创建一个新的函数

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp name={name} onClick={changeName}/>
    </div>
  );
}

父组件在调用子组件时传递了 name 属性和 onClick 属性,此时点击父组件的按钮,可以看到控制台中打印出子组件被渲染的信息。

图1

React.memo() 失效了???

分析下原因:

感觉一切又说的过去,由于子组件的 props 改变了,所以子组件渲染了,没问题呀!

回过头想一想,我们只是点击了父组件的按钮,并未对子组件做任何操作,压根就不希望子组件的 props 有变化。

useCallback 钩子进一步完善这个缺陷。

解决

修改父组件的 changeName 方法,用 useCallback 钩子函数包裹一层。

import React, { useCallback } from 'react'

function ParentComp () {
  // ...
  const [ name, setName ] = useState('hi~')
  // 每次父组件渲染,返回的是同一个函数引用
  const changeName = useCallback((newName) => setName(newName), [])  

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp name={name} onClick={changeName}/>
    </div>
  );
}

此时点击父组件按钮,控制台不会打印子组件被渲染的信息了。

究其原因:useCallback() 起到了缓存的作用,即便父组件渲染了,useCallback() 包裹的函数也不会重新生成,会返回上一次的函数引用。

React.useMemo()

问题

useMemo 又是干嘛的呢?

前面父组件调用子组件时传递的 name 属性是个字符串,如果换成传递对象会怎样?

下面例子中,父组件在调用子组件时传递 info 属性,info 的值是个对象字面量,点击父组件按钮时,发现控制台打印出子组件被渲染的信息。

import React, { useCallback } from 'react'

function ParentComp () {
  // ...
  const [ name, setName ] = useState('hi~')
  const [ age, setAge ] = useState(20)
  const changeName = useCallback((newName) => setName(newName), [])
  const info = { name, age }    // 复杂数据类型属性

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp info={info} onClick={changeName}/>
    </div>
  );
}

分析原因跟调用函数是一样的:

解决

使用 useMemo 对对象属性包一层。

useMemo 有两个参数:

function ParentComp () {
  // ....
  const [ name, setName ] = useState('hi~')
  const [ age, setAge ] = useState(20)
  const changeName = useCallback((newName) => setName(newName), [])
  const info = useMemo(() => ({ name, age }), [name, age])   // 包一层

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp info={info} onClick={changeName}/>
    </div>
  );
}

再次点击父组件按钮,控制台中不再打印子组件被渲染的信息了。

上一篇 下一篇

猜你喜欢

热点阅读