3

Whether the functional component needs to be rendered again can be optimized according to React.memo and React.useMemo.

Function component optimization-React.memo

React.memo

React.memo(ReactNode, [(prevProps, nextProps) => {}])

  • The first parameter: component
  • The second parameter [optional]: custom comparison function. If the two props are the same, it returns true, and if they are different, it returns false. Returning true will prevent the update, and returning false will re-render.

If the component is wrapped in React.memo to call, then the component renders the same result under the same props, so as to improve the performance of the component by memorizing the rendering result of the component.

React.memo only checks for props changes. By default, it will only do shallow comparison for complex objects. If you want to control the comparison process, then please pass in a custom comparison function through the second parameter.

const MyComponent = React.memo(function MyComponent(props) {
  /* 使用 props 渲染 */
}, (prevProps, nextProps) => {
  /*
  如果把 nextProps 传入 render 方法的返回结果与
  将 prevProps 传入 render 方法的返回结果一致则返回 true,
  否则返回 false
  */
})

in conclusion

When the parent component is re-rendered:

  • The child component does not use React.memo, no matter what type of child component props, the child component will be rendered repeatedly;
  • The child component uses React.memo and does not pass in the second parameter

    • When the props of the sub-component are of basic type, the sub-component will not be rendered repeatedly;
    • When the props of the sub-component are of reference type, if the props does not use the corresponding hook, the rendering will be repeated, and the sub-component will have one more diff calculation. If the corresponding hook is used, rendering will not be repeated;
  • The sub-component uses React.memo, and the comparison function is customized. Whether the sub-component is rendered repeatedly is determined by the custom function;

test

background introduction

There are two child components in a parent component. When the parent component is re-rendered, will the two child components be re-rendered under different conditions?

field explanation

Field namemeaningTest significance
titleBasic type constantTest the impact of basic type changes on sub-components
commonObjectReference type constantTest the impact of reference type changes on sub-components; test the impact of reference type changes defined by hook (useMemo) on sub-components
dataSourceReference type defined by useStateTest the impact of changes in the reference type defined by hook(useState) on child components
updateXxxxInfomethodTest the impact of the reference type change on the sub-component; test the impact of the reference type change defined by the hook (useCallBack) on the sub-component

base code

Subcomponent BaseInfo:

const BaseInfo = (props) => {
  console.log('BaseInfo 重新渲染, props:', props)
  const { title, dataSource = {} } = props

  return (
    <Card title={title}>
      <div>姓名:{dataSource.name}</div>
    </Card>
  )
}

Subcomponent OtherInfo:

const OtherInfo = (props) => {
  console.log('OtherInfo 重新渲染, props:', props)
  const { title, dataSource } = props

  return (
    <Card title={title}>
      <div>学校:{dataSource.school}</div>
    </Card>
  )
}

Parent component FunctionTest:

function FunctionTest() {
  const [baseInfo, setBaseInfo] = useState({ name: '混沌' })
  const [otherInfo, setOtherInfo] = useState({ school: '上海大学' })

  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Space>
        <Button
          onClick={() => {
            console.log('点击-修改基本信息')
            setBaseInfo({ name: '貔貅' })
          }}
        >修改基本信息</Button>
        <Button
          onClick={() => {
            console.log('点击-修改其他信息')
            setOtherInfo({ school: '北京大学' })
          }}
        >修改其他信息</Button>
      </Space>

      <BaseInfo
        title="基本信息 - 子组件"
        dataSource={baseInfo}
      />
      <OtherInfo
        title="其他信息 - 子组件"
        dataSource={otherInfo}
      />
    </Space>
  )
}

Test 1: Modify the subcomponent BaseInfo to React.memo package

const BaseInfo = React.memo((props) => {
  console.log('BaseInfo 重新渲染, props:', props)
  const { title, dataSource = {} } = props

  return (
    <Card title={title}>
      <div>姓名:{dataSource.name}</div>
    </Card>
  )
})

After clicking "Modify Basic Information", BaseInfo and OtherInfo are all re-rendered.
After clicking "modify other information", OtherInfo re-renders, but BaseInfo does not re-render.
image-20211124145500761.png

Conclusion:

  • When props is a basic type or a reference type defined by react hook (useState), using React.memo can prevent repeated rendering.
  • Using React.memo's BaseInfo, there is no repeated rendering when the props are the same.

Test two: on the basis of test one FunctionTest and pass it to the two child components

Test 2.1: When the constant of the reference type is an object/array

function FunctionTest() {
  //...
  const commonObject = {}
  //...
  return (
    // ...
    <BaseInfo
      title="基本信息 - 子组件"
      dataSource={baseInfo}
      commonObject={commonObject}
  />
    <OtherInfo
      title="其他信息 - 子组件"
      dataSource={otherInfo}
      commonObject={commonObject}
  />
    // ...
  )
}

Click "Modify Basic Information" or "Modify Other Information", BaseInfo and OtherInfo are all re-rendered.
image-20211124163004242.png

Test 2.2: When the constant of the reference type is a method:

function FunctionTest() {
  //... 
  const updateBaseInfo = () => {
    console.log('更新基本信息,原数据:', baseInfo)
    setBaseInfo({ name: '饕餮' })
  }

  const updateOtherInfo = () => {
    console.log('更新其他信息,原数据:', otherInfo)
    setOtherInfo({ school: '河南大学' })
  }
  //...
  
  return (
    //...
      <BaseInfo
        title="基本信息 - 子组件"
        dataSource={baseInfo}
                updateBaseInfo={updateBaseInfo}
      />
      <OtherInfo
        title="其他信息 - 子组件"
        dataSource={otherInfo}
                updateOtherInfo={updateOtherInfo}
      />
    //...
  )
}

Click "Modify Basic Information" or "Modify Other Information", BaseInfo and OtherInfo are all re-rendered.
image-20211124163643358.png

Conclusion:

  • When props contains reference types, using React.memo and not customizing the comparison function cannot prevent repeated rendering.
  • No matter whether React.memo is used or not, it will re-render. At this time, the performance of BaseInfo is not as good as OtherInfo, because BaseInfo has one more diff.

Test three: add hook on the basis of test two

Test 3.1: Add useMemo hook to

const commonObject = useMemo(() => {}, [])

After clicking "Modify Basic Information", BaseInfo and OtherInfo are all re-rendered.
After clicking "modify other information", OtherInfo re-renders, but BaseInfo does not re-render.
image-20211124170759018.png

Test 3.2: to updateBaseInfo and updateOtherInfo added Hook useCallback

const updateBaseInfo = useCallback(() => {
  console.log('更新基本信息,原数据:', baseInfo)
  setBaseInfo({ name: '饕餮' })
}, [])

const updateOtherInfo = useCallback(() => {
  console.log('更新其他信息,原数据:', otherInfo)
  setOtherInfo({ school: '河南大学' })
}, [])

After clicking "Modify Basic Information", BaseInfo and OtherInfo are all re-rendered.
After clicking "modify other information", OtherInfo re-renders, but BaseInfo does not re-render.
image-20211124154637824.png

Conclusion:

  • When the function of props uses useMemo/useCallback, use React.memo and do not customize the comparison function to prevent repeated rendering.
  • Using React.memo's BaseInfo, there is no repeated rendering when the props are the same.

Test 4: On the basis of Test 3, add React.memo to OtherInfo and customize the comparison function

const OtherInfo = React.memo((props) => {
  console.log('OtherInfo 重新渲染, props:', props)
  const { title, dataSource, updateOtherInfo } = props
  return (
    <Card title={title}>
      <div>学校:{dataSource.school}</div>
      <Button onClick={updateOtherInfo}>更新学校</Button>
    </Card>
  )
}, (prevProps, nextProps) => {
  console.log('OtherInfo props 比较')
  console.log('OtherInfo 老的props:', prevProps)
  console.log('OtherInfo 新的props:', nextProps)
  let flag = true
  Object.keys(nextProps).forEach(key => {
    let result = nextProps[key] === prevProps[key]
    console.log(`比较 ${key}, 结果是:${result}`)
    if (!result) {
      flag = result
    }
  })
  console.log(`OtherInfo 组件${flag ? '不会' : '会'}渲染`)
  return flag
})

After clicking "Modify Basic Information", BaseInfo is re-rendered, but OtherInfo is not re-rendered.
After clicking "Modify other information", BaseInfo is not re-rendered, and OtherInfo is re-rendered.
image-20211124171051142.png

Conclusion:

  • When the function of props uses useMemo/useCallback, use React.memo and do not customize the comparison function to prevent repeated rendering.
  • The second parameter of React.memo can determine whether custom rendering is required.

Function component optimization-React.useMemo

React.useMemo

React.useMemo(() => {}, [])

Returns a memoized value.

useMemo the "create" function and the dependency array as parameters to 061a47a7d39563, which will only recalculate the memoized value when a dependency changes.
If no dependency array is provided, useMemo will calculate a new value each time it is rendered.

in conclusion

When the parent component is re-rendered:

  • The child component does not use React.useMemo, no matter what type of child component props, the child component will be rendered repeatedly;
  • The child component uses React.useMemo, and when the value of the dependency array changes, the child component will be rendered repeatedly;

test

React.memo is a shallow comparison of props by default, and React.useMemo is a shallow comparison of dependency arrays, so the comparison results for different parameters are the same [not described in detail here].

It is recommended to use hooks such as useState, useMemo, and useCallback for reference type parameters, otherwise the shallow comparison results will be different.

时倾
794 声望2.4k 粉丝

把梦想放在心中