关于react.js:React-性能优化-避免重复渲染

7次阅读

共计 4564 个字符,预计需要花费 12 分钟才能阅读完成。

函数组件优化 – React.memo

React.memo

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

  • 第一个参数:组件
  • 第二个参数【可选】:自定义比拟函数。两次的 props 雷同的时候返回 true,不同则返回 false。返回 true 会阻止更新,而返回 false 则从新渲染。

如果把组件包装在 React.memo 中调用,那么组件在雷同 props 的状况下渲染雷同的后果,以此通过记忆组件渲染后果的形式来进步组件的性能体现。

React.memo 仅查看 props 变更,默认状况下其只会对简单对象做 浅层比照,如果想要管制比照过程,那么请将自定义的比拟函数通过第二个参数传入来实现。

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

论断

当父组件从新渲染时:

  • 当子组件的 props 是根底类型,子组件不会反复渲染。不应该应用 React.memo。
  • 当子组件的 props 是援用类型

    • 子组件未应用 React.memo。子组件会反复渲染。
    • 子组件应用 React.memo,并且不传入第二个参数。如果 props 未应用对应的 hook,那么会反复渲染,并且子组件多一次 diff 计算。如果援用类型应用对应的 hook,不会反复渲染。
    • 子组件应用 React.memo,并且传入第二个参数。子组件是否反复渲染依据自定义函数决定。

测试

背景介绍

在一个父组件里有两个子组件,当父组件产生从新渲染时,两个子组件在不同的条件管制下是否会从新渲染?

字段解释

字段名 含意 测试意义
title 根底类型常量 测试根底类型扭转对子组件的影响
commonObject 援用类型常量 测试援用类型扭转对子组件的影响;测试 hook(useMemo) 定义的援用类型扭转对子组件的影响
dataSource useState 定义的援用类型 测试 hook(useState) 定义的援用类型扭转对子组件的影响
updateXxxxInfo 办法 测试援用类型扭转对子组件的影响;测试 hook(useCallBack) 定义的援用类型扭转对子组件的影响

根底代码

子组件 BaseInfo:

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

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

子组件 OtherInfo:

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

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

父组件 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>
  )
}

测试一:批改子组件 BaseInfo 为 React.memo 包裹

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

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

点击“批改根本信息”后,BaseInfo 与 OtherInfo 全副从新渲染。
点击“批改其余信息”后,OtherInfo 从新渲染,BaseInfo 没有从新渲染。

论断:

  • 当 props 是根本类型或 react hook(useState)定义的援用类型时,应用 React.memo 能够阻止反复渲染。
  • 应用了 React.memo 的 BaseInfo,当在 props 雷同时没有反复渲染。

测试二:在测试一的根底上,在父组件 FunctionTest 中增加援用类型,并传给两个子组件

测试 2.1: 当援用类型的常量是一个对象 / 数组时

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

点击“批改根本信息”或“批改其余信息”,BaseInfo 与 OtherInfo 全副从新渲染。

测试 2.2: 当援用类型的常量是一个办法时:

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}
      />
    //...
  )
}

点击“批改根本信息”或“批改其余信息”,BaseInfo 与 OtherInfo 全副从新渲染。

论断:

  • 当 props 蕴含援用类型时,应用 React.memo 并且不自定义比拟函数时不能阻止反复渲染。
  • 无论有没有应用 React.memo 都会从新渲染,此时 BaseInfo 性能不如 OtherInfo,因为 BaseInfo 多了一次 diff。

测试三:在测试二的根底上,增加 hook

测试 3.1:给 commonObject 增加 useMemo hook

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

点击“批改根本信息”后,BaseInfo 与 OtherInfo 全副从新渲染。
点击“批改其余信息”后,OtherInfo 从新渲染,BaseInfo 没有从新渲染。

测试 3.2: 给 updateBaseInfoupdateOtherInfo 增加 useCallback hook

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

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

点击“批改根本信息”后,BaseInfo 与 OtherInfo 全副从新渲染。
点击“批改其余信息”后,OtherInfo 从新渲染,BaseInfo 没有从新渲染。

论断:

  • 当 props 的函数应用 useMemo/useCallback 时,应用 React.memo 并且不自定义比拟函数时能够阻止反复渲染。
  • 应用了 React.memo 的 BaseInfo,当在 props 雷同时没有反复渲染。

测试四:在测试三的根底上,给 OtherInfo 增加 React.memo 并且自定义比拟函数

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
})

点击“批改根本信息”后,BaseInfo 从新渲染, OtherInfo 没有从新渲染。
点击“批改其余信息”后,BaseInfo 没有从新渲染,OtherInfo 从新渲染。

论断:

  • 当 props 的函数应用 useMemo/useCallback 时,应用 React.memo 并且不自定义比拟函数时能够阻止反复渲染。
  • React.memo 的第二个参数能够判断是否须要自定义渲染。
正文完
 0