2016-07-23 1 views
1

Представьте компонент List, который отображает список элементов и, при необходимости, выделяет один из них.Как оптимизировать render() в React, когда prop является объектом?

enter image description here

List имеет следующие реквизиты:

  • items - элементы для отображения
  • highlightedItemIndex - индекс выделенного элемента (или null, если ни один не будет выделен)
  • itemProps - произвольный объект реквизита для прохождения до каждого предмета

List 's render() метод выглядит следующим образом:

<ul> 
    { 
    items.map((item, index) => { 
     const allItemProps = { 
     ...itemProps, 
     className: index === highlightedItemIndex ? 'highlighted-item' : '' 
     }; 

     return (
     <Item item={item} itemProps={allItemProps} key={index} /> 
    ); 
    }) 
    } 
</ul> 

Проблема в том, что каждый раз, когда List' s render() называется, Item получает новый объект для itemProps вызывая Item «s render() называться , что не является необходимым в большинстве случаев, потому что itemProps на самом деле не изменился (он глубоко соответствует предыдущему itemProps, но не ===).

Представьте изображение выше с 1000 элементами, и, когда вы наведете объекты, выделенный элемент изменится соответствующим образом. Каждый раз, когда выделенный элемент меняется, Itemrender() следует вызывать дважды, а не 1000 раз!

Добавление:

shouldComponentUpdate(nextProps) { 
    return JSON.stringify(nextProps) !== JSON.stringify(this.props); 
} 

решает эту проблему, но я чувствую, что JSON.stringify подход может быть довольно дорогим.

Here is a playground Я создал, чтобы продемонстрировать проблему. (У меня есть 5 элементов в списке, а не 1000, но вы получите эту идею.)

Что такое «Реагировать», чтобы оптимизировать рендеринг, когда опорный объект является объектом (inputProps в этом случае), или, может быть, реквизит объекта не хорошая практика?

+0

Лучшим было бы убедиться, что 'item' - это тот же объект. Я думаю, что в большинстве случаев это возможно. –

+0

В вашем примере, похоже, вы можете избежать использования 'allItemsProps'. Возможно, это может быть ответственностью «Элемента», чтобы знать, какой класс должен быть выделен, а «highlight» может быть свойством элемента. –

+0

Возможно, lodash isEqual feets для вас http://www.webpackbin.com/VkbwSLhPW –

ответ

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} .../>. Таким образом, на уровне предмета может возникнуть мелкое сравнение.Тем не менее, я думал, что есть некоторые другие вещи, которые можно улучшить, поэтому я изменил немного больше.

Некоторые примечания:

  • По умолчанию реквизита обычно делается лучше с static defaultProps = {…} вместо присваивания по умолчанию, когда в уничтожение того, метод визуализации.
  • Компонент App, вероятно, не должен касаться событий мыши, а скорее предоставлять более семантические обратные вызовы, такие как onHighlightedItemChange. В зависимости от вашего варианта использования вы также можете подумать о перемещении состояния, в котором элемент списка подсвечивается до List.
  • Я думаю, что имеет смысл, что Item управляет своим собственным именем класса, если родительский компонент не переопределяет некоторые стили. Поэтому я вычислил его имя класса из семантического свойства isHighlighted. Возможно, isHighlighted может также использоваться для некоторых других различий, независимо от названия класса, в будущем.
  • Мы также можем сделать <Item onMouseEnter={this.props.onHighlightedItemChange.bind(null, index)} /> в методе визуализации List. Это несколько распространено, так как дочерний компонент не должен знать о его index. Однако в вашем случае, поскольку производительность, похоже, вызывает беспокойство, лучше не делать этого, поскольку вызов bind создаст новую функцию для каждого рендеринга, поэтому всегда требуется повторная визуализация при сравнении shouldComponentUpdate.

Надеюсь, это поможет!

Если вы собираетесь использовать мой код, обязательно используйте пакет npm из react-addons-pure-render-mixin вместо моего скопированного кода. Btw. вероятно, скоро будет React.PureComponent, который по умолчанию будет реализовывать shouldComponentUpdate - это пригодится для вашего дела.

0

У вас есть правильная идея. Просто настройте таргетинг на значение, которое действительно определяет эту ситуацию.

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

Пользователь может предоставить произвольные элементы реквизита, которые следует учитывать при рендеринге. Например, у вас может быть 'itemProps.title', который меняется и должен быть отражен путем рендеринга. Если вы просто проверите изменения 'inputProps.className' и' inputProps.title', пользовательский интерфейс не будет обновляться, что не так. –

+0

Вы правы, и я не принял во внимание это. В shouldComponentUpdate Я буду явно определять, какие значения itemProps следует учитывать. Лично я бы пошел только с вашим способом 'JSON.stringify', если itemProps огромен, и все значения itemProps повлияют на ** рендеринг **. – Jason

Смежные вопросы