重新呈现列表的单行而不重新呈现整个列表

时间:2016-03-31 10:28:57

标签: performance reactjs ecmascript-6 material-ui

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

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

您可以看到问题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>
}

提前谢谢。

1 个答案:

答案 0 :(得分:4)

您可以将行包装到实现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这样的lib,它提供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会按顺序进行:)