2015-06-22 2 views
24

Я принял пример TodoList, чтобы отразить мою проблему, но, очевидно, мой код в реальном мире более сложный.React performance: рендеринг большого списка с PureRenderMixin

У меня есть такой псевдокод.

var Todo = React.createClass({ 
    mixins: [PureRenderMixin], 
    ............ 
} 

var TodosContainer = React.createClass({ 
    mixins: [PureRenderMixin],  

    renderTodo: function(todo) { 
    return <Todo key={todo.id} todoData={todo} x={this.props.x} y={this.props.y} .../>; 
    }, 

    render: function() { 
    var todos = this.props.todos.map(this.renderTodo) 
    return (
      <ReactCSSTransitionGroup transitionName="transition-todo"> 
       {todos} 
      </ReactCSSTransitionGroup>, 
    ); 
    } 

}); 

Все мои данные неизменяемы, и PureRenderMixin используется надлежащим образом, и все работает нормально. Когда данные Todo изменяются, повторно отображается только родительский и отредактированный todo.

Проблема в том, что в какой-то момент мой список растет, когда пользователь прокручивается. И когда обновляется один Todo, требуется все больше и больше времени для рендеринга родителя, вызовите shouldComponentUpdate на всех тодосах, а затем визуализируйте одиночное todo.

Как вы можете видеть, компонент Todo имеет другой компонент, отличный от данных Todo. Это данные, которые необходимы для рендеринга всеми todos и являются общедоступными (например, мы могли предположить, что для todos есть «displayMode»). Наличие многих свойств делает работу shouldComponentUpdate немного медленнее.

Кроме того, с помощью ReactCSSTransitionGroup кажется замедлить немного тоже, как ReactCSSTransitionGroup должны оказывать себя и ReactCSSTransitionGroupChild еще до shouldComponentUpdate из несделанного называется. React.addons.Perf показывает, что рендеринг ReactCSSTransitionGroup > ReactCSSTransitionGroupChild - это время, потраченное впустую для каждого элемента списка.

Итак, насколько я знаю, я использую PureRenderMixin, но с большим списком этого может быть недостаточно. Я по-прежнему получаю не очень плохие представления, но хотел бы знать, есть ли простые способы оптимизации моих рендерингов.

Любая идея?


Edit:

До сих пор мой большой список разбивается на страницы, так что вместо того, чтобы иметь большой список вещей, я теперь разделить этот большой список в списке страниц. Это позволяет иметь лучшие характеристики, поскольку каждая страница может теперь реализовать shouldComponentUpdate. Теперь, когда элемент изменяется на странице, React должен вызывать основную функцию рендеринга, которая выполняет итерацию на странице, и вызывать функцию рендеринга только с одной страницы, что делает работу с меньшим количеством итераций.

Тем не менее, мои показатели рендера по-прежнему линейны для номера страницы (O (n)) У меня есть. Так что, если у меня тысячи страниц, это все равно та же проблема :) В моем случае, это вряд ли произойдет, но меня все еще интересует лучшее решение.

Я уверен, что можно достичь производительности рендеринга O (log (n)), где n - количество элементов (или страниц), путем разбиения большого списка на дерево (например, постоянная структура данных) и где каждый узел имеет возможность короткого замыкания вычисления с shouldComponentUpdate

Да, я имею в виду что-то похожее на постоянные структуры данных, как вектор в Scala или Clojure:

http://hypirion.com/imgs/pvec/9-annotated.png

Однако я м обеспокоены реакцией, потому что, насколько я знаю, возможно, потребуется создать промежуточный дом узлов при рендеринге внутренних узлов дерева.Это может быть проблемой в соответствии с usecase (and may be solved in future versions of React)

Также, поскольку мы используем Javascript, мне интересно, поддерживает ли Immutable-JS это и делает доступными «внутренние узлы». См: https://github.com/facebook/immutable-js/issues/541

Edit: полезные ссылки с моими экспериментами: Can a React-Redux app really scale as well as, say Backbone? Even with reselect. On mobile

+0

Сначала нам нужно определить «большой», и после этого нам нужно понять, что чем больше элементов, вставленных в DOM, тем медленнее производительность. В сочетании с переходами CSS вы ожидаете более медленную производительность в целом. Также PureRenderMixin будет работать только в том случае, если контент, который вы визуализируете, постоянно _exact_. –

+2

Используете ли вы 'ключ' для своих' Тодо'? –

+0

Вы найдете это полезным. http://stackoverflow.com/questions/34983963/performance-issue-with-react – user730009

ответ

5

Вот реализация POC, которую я сделал с внутренней структурой ImmutableJS. Это не общедоступный API, поэтому он не готов к производству и в настоящее время не обрабатывает угловые случаи, но он работает.

var ImmutableListRenderer = React.createClass({ 
    render: function() { 
    // Should not require to use wrapper <span> here but impossible for now 
    return (<span> 
     {this.props.list._root ? <GnRenderer gn={this.props.list._root}/> : undefined} 
     {this.props.list._tail ? <GnRenderer gn={this.props.list._tail}/> : undefined} 
</span>); 
    } 
}) 

// "Gn" is the equivalent of the "internal node" of the persistent data structure schema of the question 
var GnRenderer = React.createClass({ 
    shouldComponentUpdate: function(nextProps) { 
     console.debug("should update?",(nextProps.gn !== this.props.gn)); 
     return (nextProps.gn !== this.props.gn); 
    }, 
    propTypes: { 
     gn: React.PropTypes.object.isRequired, 
    }, 
    render: function() { 
     // Should not require to use wrapper <span> here but impossible for now 
     return (
      <span> 
       {this.props.gn.array.map(function(gnItem,index) { 
        // TODO should check for Gn instead, because list items can be objects too... 
        var isGn = typeof gnItem === "object" 
        if (isGn) { 
         return <GnRenderer gn={gnItem}/> 
        } else { 
         // TODO should be able to customize the item rendering from outside 
         return <span>{" -> " + gnItem}</span> 
        } 
       }.bind(this))} 
      </span> 
     ); 
    } 
}) 

клиентский код выглядит

React.render(
    <ImmutableListRenderer list={ImmutableList}/>, 
    document.getElementById('container') 
); 

Вот JsFiddle который регистрирует количество shouldComponentUpdate вызовов после того, как один элемент списка (размер N) обновляется: это не требует, чтобы позвонить N раз shouldComponentUpdate

Дальнейшие подробности реализации являются общими в этом ImmutableJs github issue

8

В нашем продукте мы также были вопросы, связанные с количеством коды визуализируются, и мы начали использовать наблюдаемые (см это blog). Это может частично решить вашу проблему, так как изменение todo больше не потребует родительского компонента, который содержит список, который нужно перерисовать (но добавление все еще делает).

Он также может помочь вам в повторном рендеринге списка быстрее, так как ваши компоненты todoItem могут просто вернуть false, когда реквизит изменится на shouldComponentUpdate.

Для улучшения производительности при рендеринге обзора, я думаю, что ваша идея tree/paging действительно хороша. С наблюдаемыми массивами каждая страница может начать прослушивать массивные сращивания (используя ESF-полиполк или mobservable) в определенном диапазоне. Это было бы ввести некоторые администрации, поскольку эти диапазоны могут изменяться с течением времени, но должен заставить вас O (журнал (п))

Таким образом, вы получите что-то вроде:

var TodosContainer = React.createClass({ 
    componentDidMount() { 
    this.props.todos.observe(function(change) { 
     if (change.type === 'splice' && change.index >= this.props.startRange && change.index < this.props.endRange) 
      this.forceUpdate(); 
    }); 
    },  

    renderTodo: function(todo) { 
    return <Todo key={todo.id} todoData={todo} x={this.props.x} y={this.props.y} .../>; 
    }, 

    render: function() { 
    var todos = this.props.todos.slice(this.props.startRange, this.props.endRange).map(this.renderTodo) 
    return (
      <ReactCSSTransitionGroup transitionName="transition-todo"> 
       {todos} 
      </ReactCSSTransitionGroup>, 
    ); 
    } 

}); 

центральной проблемы с большими списками и реагировать, кажется, что вы не можете просто переместить новые узлы DOM в dom. В противном случае вам не понадобятся «страницы», чтобы разделить данные на более мелкие куски, и вы могли бы просто соединить один новый элемент Todo в dom, как это сделано с JQuery в этом jsFiddle. Вы все еще можете это сделать, если будете реагировать, если вы используете ref для каждого пункта todo, но это будет работать вокруг системы, я думаю, так как это может нарушить систему согласования?

+1

Привет.Понимайте свою точку зрения, но это отчасти поражает цель, которую мы пытаемся достичь в нашем стартапе, где мы всегда пересматриваем с самого верха, и ничего не наблюдаем, кроме глобальных неизменных государственных свопов. Я хочу проверить пределы этой более простой модели, прежде чем вводить что-либо наблюдаемое вообще. До сих пор он не работает так плохо, даже если мы, вероятно, не имеем лучшей производительности. См. Также https://www.youtube.com/watch?v=zxN8FYYBcrI –

0

Недавно я имел ар erxtance, пытающееся отобразить таблицу с более 500 записями, редукторы были неизменными, и я использовал повторный выбор для memoize сложных селекторов, после того, как вытащил некоторые волосы, я обнаружил, что проблема решена, замещая все селекторы.

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