最近写表格相关需求的时候,发现点击一行的checkbox后,卡顿很久才会选上。于是对表格进行了优化。这种数据偏多导致交互时卡顿的情况很常见,主要是组件渲染的问题,所以研究下怎么从渲染方面优化应用性能。

经过研究发现主要原因在于:每当点击一个checkbox时,Table内所有组件,如TableRow、TableHeader、TablePagination全部重新渲染了一遍!造成了严重的卡顿。所以我们要做的就是减少无用的渲染。
最理想的情况是:当UI变化时,只需要真正变化的那个组件重渲染,其他组件一律不渲染。

方法

1.不要在render方法中定义组件

  1. // 以下为错误示范,万不可取!
  2. class Table extends Component {
  3. render() {
  4. const TableRow = (data) => <tr>...</tr>
  5. const rows = this.props.data.map(item => <TableRow data={item} />)
  6. return (
  7. <TableHeader />
  8. {rows}
  9. )
  10. }
  11. }
  12. // 正确的操作
  13. const TableRow = (data) => <tr>...</tr>
  14. class Table extends Component {
  15. render() {
  16. return (
  17. <TableHeader />
  18. {
  19. this.props.data.map(item => <TableRow data={item} />)
  20. }
  21. )
  22. }
  23. }

原因:若TableRow组件定义在Table的render方法里,只要Table的props或state一旦修改,导致Table执行render函数,进一步导致TableRow和rows都是新的、全部的TableRow全部重渲染
解决方式:

  1. 单独定义组件
  2. render里不要定义多余的变量,如rows,尽可能直接写在return的jsx里

2.使用PureComponent、memo

React.PureComponent作用是只要组件的props、state不变,父组件更新不会让组件随之更新,适用于class组件。
React.memo作用同理,适用于函数式组件。

  1. // class组件
  2. class TableRow extends React.PureComponent {
  3. render() {
  4. return (...)
  5. }
  6. }
  7. // 函数式组件
  8. const TableRow = React.memo(() => {
  9. return (...)
  10. })

例子:本案例中使用了Material UI v4.9,其对组件的封装并没有使用PureComponent,导致TableRow组件重渲染时,每个单元格也重渲染。
解决方法:在组件外包一层memo控制渲染。

  1. // 以下为错误示范
  2. class TableRow extends React.PureComponent {
  3. render() {
  4. return (
  5. <TableCell>
  6. <a href=''>{name}</a>
  7. </TableCell>
  8. <TableCell>{gender}</TableCell>
  9. <TableCell>{age}</TableCell>
  10. )
  11. }
  12. }
  13. // 正确的操作
  14. const TableCell = React.memo((props) => <MuiTableCell ...props />)
  15. class TableRow extends React.PureComponent {
  16. render() {
  17. return (
  18. <TableCell>
  19. <a href=''>{name}</a>
  20. </TableCell>
  21. <TableCell>{gender}</TableCell>
  22. <TableCell>{age}</TableCell>
  23. )
  24. }
  25. }

3.尽可能传递基础类型数据、不变的数据

若传递的props是引用类型,则props若内容不变,引用变了,则导致组件无意义的渲染。

例子一:比如Table用一个数组 selectedRows存储当前选择的row,然后将selectedRows传给每一行TableRow,用于判断该TableRow是否被选择。每次选择后,希望只有新选择的row重渲染,而实际上使用setState更新state后,selectedRows为新数组,导致全部row都重新渲染。
解决方法:用布尔值isSelected代替,在TableRow组件外判断是否被选择。这样在不断切换选择row时,传递的参数一直是false,不会触发渲染。

  1. // 以下为错误示范,万不可取!
  2. class Table extends Component {
  3. render() {
  4. return (
  5. {
  6. this.props.data.map(item =>
  7. <TableRow data={item} selectedRows={selectedRows} />)
  8. }
  9. )
  10. }
  11. }
  12. // 正确的操作
  13. class Table extends Component {
  14. render() {
  15. return (
  16. {
  17. this.props.data.map(item => {
  18. const isSelected = selectedRows.includes(item.id)
  19. return <TableRow data={item} isSelected={isSelected} />
  20. })
  21. }
  22. )
  23. }
  24. }

例子二:在props很多时,有人会喜欢把同一类的props组成一个对象,传给子组件(如下)。但这样带来的问题就是每次Table重渲染时,{page,count: dataCount,rowsPerPage: rowsPerPage}会组成新对象,会导致Pagination也重渲染
解决方式:props拆开传递,传递基本类型数据

  1. // 以下为错误示范,万不可取!
  2. class Table extends Component {
  3. render() {
  4. return (
  5. ...
  6. <TablePagination
  7. pagination={{
  8. page,
  9. count: dataCount,
  10. rowsPerPage: rowsPerPage
  11. }}
  12. />
  13. )
  14. }
  15. }
  16. // 正确的操作
  17. class Table extends Component {
  18. render() {
  19. return (
  20. ...
  21. <TablePagination
  22. page={page}
  23. count={dataCount}
  24. rowsPerPage={rowsPerPage}
  25. />
  26. )
  27. }
  28. }

4.组件分割

组件的子组件也属于组件props一部分,当是string等基础类型时,不会导致重渲染;若是node类型,就算数据没有变化,也会产生新node重渲染。
解决方法:分割成小组件,memo包上。

  1. // 以下为错误示范
  2. class TableRow extends React.PureComponent {
  3. render() {
  4. return (
  5. <TableCell>
  6. <a href=''>{name}</a>
  7. </TableCell>
  8. <TableCell>{gender}</TableCell>
  9. <TableCell>{age}</TableCell>
  10. )
  11. }
  12. }
  13. // 正确的操作
  14. const TableCell = React.memo((props) => <MuiTableCell ...props />)
  15. const NameTabelCell = React.memo(({ name }) =>
  16. <TableCell>
  17. <a href=''>{name}</a>
  18. </TableCell>
  19. )
  20. class TableRow extends React.PureComponent {
  21. render() {
  22. return (
  23. <NameTabelCell name={name} />
  24. <TableCell>{gender}</TableCell>
  25. <TableCell>{age}</TableCell>
  26. )
  27. }
  28. }

总结

引起组件重渲染的原因

  1. 父组件传递props变化
  2. 组件内部state变化
  3. 父组件重渲染
  4. 组件在父组件内重新生成了