【问题标题】:Re-rendering a single row of a list without re-rendering the entire list重新渲染列表的单行而不重新渲染整个列表
【发布时间】:2016-03-31 10:28:57
【问题描述】:

我们正在尝试使用material-ui 实现一个与the new Material Design Google Contacts 类似的联系人列表(您必须启用材料设计皮肤才能看到它)。
具体来说,我们试图在行悬停时显示一个复选框而不是头像。
我们只想捕获并重新渲染感兴趣的行(悬停时)并相应地显示头像/复选框......这似乎是一项简单的任务,但我们无法将渲染隔离到悬停的行(而不是重新渲染整个列表) 你对如何做这样的事情有什么建议吗?

我们的临时解决方案使用处理表的容器组件: 当悬停一行时,我们从Table 组件的onRowHover 捕获它并将其保存在容器状态中。这会触发性能非常差的整个列表的重新渲染。

您可以观看该问题的视频here。

这是一个代码示例:

import React from 'react'
import Avatar from 'material-ui/lib/avatar'
import Checkbox from 'material-ui/lib/checkbox'
import Table from 'material-ui/lib/table/table'
import TableHeaderColumn from 'material-ui/lib/table/table-header-column'
import TableRow from 'material-ui/lib/table/table-row'
import TableHeader from 'material-ui/lib/table/table-header'
import TableRowColumn from 'material-ui/lib/table/table-row-column'
import TableBody from 'material-ui/lib/table/table-body'
import R from 'ramda'

export default class ContactsList extends React.Component {

  constructor (props) {
    super(props)
    this.state = { hoveredRow: 0 }
    this.contacts = require('json!../../public/contacts.json').map((e) => e.user) // Our contact list array
  }

  _handleRowHover = (hoveredRow) => this.setState({ hoveredRow })

  _renderTableRow = ({ hovered, username, email, picture }) => {
    const checkBox = <Checkbox style={{ marginLeft: 8 }} />
    const avatar = <Avatar src={picture} />
    return (
      <TableRow key={username}>
        <TableRowColumn style={{ width: 24 }}>
          {hovered ? checkBox : avatar}
        </TableRowColumn>
        <TableRowColumn>{username}</TableRowColumn>
        <TableRowColumn>{email}</TableRowColumn>
      </TableRow>
    )
  }

  render = () =>
    <Table
      height='800px'
      fixedHeader
      multiSelectable
      onRowHover={this._handleRowHover}
    >
      <TableHeader displaySelectAll enableSelectAll>
        <TableRow>
          <TableHeaderColumn>Nome</TableHeaderColumn>
          <TableHeaderColumn>Email</TableHeaderColumn>
          <TableHeaderColumn>Telefono</TableHeaderColumn>
        </TableRow>
      </TableHeader>
      <TableBody displayRowCheckbox={false} showRowHover>
        {this.contacts.map((contact, index) => this._renderTableRow({
          hovered: index === this.state.hoveredRow,
          ...contact }))
        }
      </TableBody>
    </Table>
}

提前谢谢你。

【问题讨论】:

    标签: performance reactjs ecmascript-6 material-ui


    【解决方案1】:

    你可以像这样将你的行包装到一个实现shouldComponentUpdate的新组件中:

    class ContactRow extends Component {
        shouldComponentUpdate(nextProps) {
            return this.props.hovered !== nextProps.hovered || ...; // check all props here
        }
        render() {
            const { username, email, ...otherProps } = this.props;
            return (
                <TableRow { ...otherProps } >
                    <TableRowColumn style={{ width: 24 }}>
                        {this.props.hovered ? checkBox : avatar}
                    </TableRowColumn>
                    <TableRowColumn>{this.props.username}</TableRowColumn>
                    <TableRowColumn>{this.props.email}</TableRowColumn>
                 </TableRow>
            );
        }
    }
    

    然后你可以像这样在ContactList 组件中使用它:

    this.contacts.map((contact, index) => <ContactRow key={contact.username} {...contact} hovered={index === this.state.hoveredRow} />)
    

    如果您不想手动实现 shouldComponentUpdate,可以使用 React 的 PureRenderMixin 或检查像 recompose 这样的库,它提供了像 pure 这样有用的帮助器。

    编辑

    正如 OP 和 @Denis 所指出的,上述方法不能很好地与 Table 组件的某些功能配合使用。具体来说,TableBody 对其孩子的孩子进行了一些操作。更好的方法是像这样定义您的 ContactRow 组件:

    class ContactRow extends Component {
        shouldComponentUpdate(nextProps) {
            // do your custom checks here
            return true;
        }
        render() {
            const { username, email, ...otherProps } = this.props;
            return <TableRow { ...otherProps } />;
        }
    }
    

    然后像这样使用它

    <ContactRow { ...myProps }>
        <TableRowColumn>...</TableRowColumn>
    </ContactRow>
    

    但我猜TableRow 仅在必要时重新渲染是每个人都会受益的功能,所以可能需要 PR :)

    【讨论】:

    • 感谢您的建议,我们已经尝试过这种方法,但它会导致另一个问题:TableRow 和 Table 是严格绑定的,需要正确嵌套才能工作。用 React 组件包装 TableRow 会破坏很多事情(例如,tableRow 的悬停和选择不会正确传播到 Table 组件)。
    • @MatteoMazzarolo 好的,我已经编辑了我的答案以考虑到这一点。在这种情况下,您需要将 TableBody 组件传递给它的任何道具传回 TableRow。
    • 谢谢。传递 TableBody 道具无法正常工作,它触发了在行悬停时重新渲染整个列表。不幸的是,这是一个时间敏感的任务,所以我们正在切换到另一个布局,我将在接下来的几天进一步调查。无论如何,我将您的答案标记为正确,因为我认为不会有更好的解决方案。我希望被证明是错误的:)
    • 谢谢!我不知道 material-ui,但我觉得肯定还有另一个问题,因为这似乎有效:gist.github.com/VonD/3c7d30ea39447de02ff968fcf416cd82(仅重新渲染所需的 ContactRow 元素)
    • @Denis 我已经编辑了答案。希望对你有用。
    猜你喜欢
    • 2020-11-15
    • 1970-01-01
    • 2020-10-30
    • 1970-01-01
    • 1970-01-01
    • 2021-12-12
    • 2017-10-31
    • 1970-01-01
    • 2017-09-09
    相关资源
    最近更新 更多