如果prop是一个对象,如何在React中优化render()?

时间:2016-07-23 10:33:36

标签: reactjs

想象一个List组件,它会呈现一个项目列表,并可选择突出显示其中一个项目。

enter image description here

List有以下道具:

  • items - 要显示的项目
  • highlightedItemIndex - 突出显示的项目的索引(或null,如果没有突出显示)
  • itemProps - 任意道具对象传递给每个项目

List的{​​{1}}方法如下所示:

render()

问题是,每次<ul> { items.map((item, index) => { const allItemProps = { ...itemProps, className: index === highlightedItemIndex ? 'highlighted-item' : '' }; return ( <Item item={item} itemProps={allItemProps} key={index} /> ); }) } </ul> List被调用时,render()都会获得导致Item itemProps Item {{}}}的新对象1}}被调用,这在大多数情况下是不必要的,因为render()没有真正改变(它深度等于前一个itemProps,而不是itemProps)。

想象一下上面的图片有1000个项目,当您悬停项目时,突出显示的项目会相应更改。每次突出显示的项目发生变化时,===的{​​{1}}应该被调用两次,而不是1000次!

添加:

Item

解决了这个问题,但我觉得render()方法可能非常昂贵。

我创建了

Here is a playground来证明这个问题。 (我在列表中有5个项目而不是1000个,但你明白了。)

当prop是一个对象(在这种情况下是shouldComponentUpdate(nextProps) { return JSON.stringify(nextProps) !== JSON.stringify(this.props); } )时,优化渲染的“React方式”是什么,或者对象道具不是一个好习惯?

2 个答案:

答案 0 :(得分:1)

感谢您分享CodePen - 这对答案有很大帮助。

我已经更新了你的笔,所以它只在必要时呈现:http://codepen.io/amann/pen/dXZpyz?editors=0010

来自顶部的PureRenderMixin的一些复制粘贴代码,请向下滚动,直到看到Relevant code starts here

以下是最相关的部分:

class Item extends Component {
  constructor(props) {
    super(props);

    this.onMouseEnter = this.onMouseEnter.bind(this);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
  }

  onMouseEnter() {
    this.props.onMouseEnter(this.props.index);
  }

  render() {
    const { item, isHighlighted } = this.props;

    itemRenderCounter++;

    let className = 'item';
    if (isHighlighted) className += ' highlighted-item';

    return (
      <li className={className} onMouseEnter={this.onMouseEnter}>
        {item}
      </li>
    );
  }
}

class List extends Component {
  static defaultProps = {
    items: []
  };

  constructor(props) {
    super(props);
    this.onMouseLeave = this.onMouseLeave.bind(this);
    this.onItemMouseEnter = this.onItemMouseEnter.bind(this);
  }

  onMouseLeave() {
    this.props.onHighlightedItemChange(null);
  }

  onItemMouseEnter(index) {
    this.props.onHighlightedItemChange(index);
  }

  render() {
    const { items, highlightedItemIndex, onHighlightedItemChange} = this.props;

    return (
      <ul className="list-container" onMouseLeave={this.onMouseLeave}>
        {items.map((item, index) =>
          <Item
            key={index}
            index={index}
            item={item}
            isHighlighted={index === highlightedItemIndex}
            onMouseEnter={this.onItemMouseEnter}
          />
        )}
      </ul>
    );
  }
}

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {highlightedItemIndex: 1};
    this.onHighlightedItemChange = this.onHighlightedItemChange.bind(this);
  }

  onHighlightedItemChange(index) {
    this.setState({highlightedItemIndex: index});
  }

  render() {
    const { highlightedItemIndex } = this.state;

    return (
      <div>
        <List
          items={fruits}
          highlightedItemIndex={highlightedItemIndex}
          onHighlightedItemChange={this.onHighlightedItemChange}
        />
        <div className="counter">
          Item render() counter: {itemRenderCounter}
        </div>
      </div>
     );
  }
}

我认为您的初始问题可以通过使用扩展运算符<Item {...itemProps} .../>来解决。这样就可以在Item级别进行浅层比较。但是,我认为还有其他一些事情可以改进,所以我改变了一点。

一些注意事项:

  • 在渲染方法中进行解构时,默认道具通常使用static defaultProps = {…}而不是默认赋值更好。
  • App组件可能不应该关注鼠标事件,而是提供一些更多的语义回调,如onHighlightedItemChange。根据您的使用案例,您还可以考虑将列表项突出显示的状态移至List
  • 我认为Item管理自己的类名是有意义的,只要父组件不覆盖某些样式。因此,我从语义属性isHighlighted计算了它的类名。也许isHighlighted也可以用于其他一些差异,将来可以独立于班级名称。
  • 我们也可以在<Item onMouseEnter={this.props.onHighlightedItemChange.bind(null, index)} />的呈现方法中执行List。这有点常见,因为子组件不必知道其index。但是在你的情况下,由于性能似乎是一个问题,最好不要这样,因为bind调用将在每个渲染上创建一个新函数,因此总是需要在比较中重新渲染shouldComponentUpdate

希望这有帮助!

如果您要使用我的代码,请务必使用react-addons-pure-render-mixin的npm包而不是我的复制粘贴代码。顺便说一句。可能很快就会成为一个React.PureComponent默认情况下会shouldComponentUpdate - 这对你的情况会派上用场。

答案 1 :(得分:0)

你有正确的想法。只需定位真正定义此情况的值

shouldComponentUpdate(nextProps) {
  return nextProps.itemProps.className !== this.props.itemProps.className;
}