ReactJS. Довольно медленно при рендеринге и обновлении простого списка из 1500 элементов <li>. Я думал, что VirtualDOM быстр

Я был очень разочарован результатами, которые я получил на следующем простом примере ReactJS. При нажатии на элемент метка (счет) обновляется соответствующим образом. К сожалению, для обновления требуется примерно ~ 0,5-1 секунды. Это в основном из-за "повторного рендеринга" всего списка todo.

Мое понимание заключается в том, что решение для решения ключевых требований заключается в том, чтобы API выглядел так, как будто он повторно отображает все приложение при каждом обновлении. Предполагается взять текущее состояние DOM и сравнить его с целевым представлением DOM, выполнить diff и обновить только те вещи, которые необходимо обновить.

Я делаю то, что не является оптимальным? Я всегда мог обновить метку счета вручную (и состояние молча), и это будет почти мгновенная операция, но это уберет точку использования ReactJS.

/** @jsx React.DOM */

TodoItem = React.createClass({

    getDefaultProps: function () {
        return {
            completedCallback: function () {
                console.log('not callback provided');
            }
        };
    },
    getInitialState: function () {
        return this.props;
    },

    updateCompletedState: function () {
        var isCompleted = !this.state.data.completed;
        this.setState(_.extend(this.state.data, {
            completed: isCompleted
        }));
        this.props.completedCallback(isCompleted);
    },

    render: function () {
        var renderContext = this.state.data ?
            (<li className={'todo-item' + (this.state.data.completed ? ' ' + 'strike-through' : '')}>
                <input onClick={this.updateCompletedState} type="checkbox" checked={this.state.data.completed ? 'checked' : ''} />
                <span onClick={this.updateCompletedState} className="description">{this.state.data.description}</span>
            </li>) : null;

        return renderContext;
    }
});


var TodoList = React.createClass({
    getInitialState: function () {
        return {
            todoItems: this.props.data.todoItems,
            completedTodoItemsCount: 0
        };
    },

    updateCount: function (isCompleted) {
        this.setState(_.extend(this.state, {
            completedTodoItemsCount: isCompleted ? this.state.completedTodoItemsCount + 1 : this.state.completedTodoItemsCount - 1
        }));
    },
    
    render: function () {
        var updateCount = this.updateCount;
        return (
            <div>
                <div>count: {this.state.completedTodoItemsCount}</div>
                <ul className="todo-list">
                    { this.state.todoItems.map(function (todoItem) {
                        return <TodoItem data={ todoItem } completedCallback={ updateCount } />
                    }) }
                </ul>
            </div>
        );
    }
});

var data = {todoItems: []}, i = 0;

while(i++ < 1000) {
	data.todoItems.push({description: 'Comment ' + i, completed: false});
}

React.renderComponent(<TodoList data={ data } />, document.body);
<script src="http://fb.me/react-js-fiddle-integration.js"></script>

Ответ 1

Если вы сделаете следующее, вы можете сократить время на много. Он проводит от 25 до 45 мс для обновления для меня.

  • используйте производственную сборку
  • реализовать shouldComponentUpdate
  • обновить состояние неизменно
updateCompletedState: function (event) {
    var isCompleted = event.target.checked;
    this.setState({data: 
        _.extend({}, this.state.data, {
            completed: isCompleted
       })
    });
    this.props.completedCallback(isCompleted);
},

shouldComponentUpdate: function(nextProps, nextState){
    return nextState.data.completed !== this.state.data.completed;
},

Обновлена ​​скрипка

(в этом коде есть много сомнительных вещей, daniula указывает на некоторые из них)

Ответ 2

  • Когда вы создаете список элементов, вы должны предоставить уникальный ключ для всех. В вашем случае:

    <ul className="todo-list">
        { this.state.todoItems.map(function (todoItem, i) {
            return <TodoItem key={i} data={ todoItem } completedCallback={ updateCount } />
        }) }
    </ul>
    

    Вы можете узнать об этой ошибке с помощью предупреждающего сообщения в консоли браузера:

    Каждый ребенок в массиве должен иметь уникальную "ключевую" опору. Проверьте метод рендеринга TodoList. Подробнее см. fb.me/react-warning-keys.

  • Существует еще одно предупреждение, которое вы можете легко исправить, изменив обработчик событий на <input type="checkbox" /> внутри <TodoItem /> от onClick до onChange:

    <input onClick={this.updateCompletedState} type="checkbox" checked={this.state.data.completed ? 'checked' : ''} />
    
  • Вы выполняете конкатенацию строк, чтобы установить правильный className. Для более читаемого кода попробуйте использовать красивые и простые React.addons.classSet:

    render: function () {
        var renderContext = this.state.data ?
            var cx = React.addons.classSet({
                'todo-item': true,
                'strike-through': this.state.data.completed
            });
    
            (<li className={ cx }>
                <input onChange={this.updateCompletedState} type="checkbox" checked={this.state.data.completed ? 'checked' : ''} />
                <span onClick={this.updateCompletedState} className="description">{this.state.data.description}</span>
            </li>) : null;
    
        return renderContext;
    }
    

Ответ 3

Я ищу, где вы выполняете() список...

<div>
   <div>count: {this.state.completedTodoItemsCount}</div>
   <ul className="todo-list">
        { this.state.todoItems.map(function (todoItem) {
            return <TodoItem data={ todoItem } completedCallback={ updateCount } />
        }) }
   </ul>
</div>

Это нельзя вызывать каждый раз, когда обновляется TodoItem. Дайте указанному элементу окружающие div и a id следующим образом:

return <div id={someindex++}><TodoItem
  data={ todoItem }
  completedCallback={ updateCount }
/></div>

Затем просто перезагрузите один TodoItem, поскольку он изменен, например:

ReactDOM.render(<TodoItem ...>, document.getElementById('someindex'));

ReactJS должен быть быстрым, да, но вам все равно нужно придерживаться общих парадигм программирования, то есть просить машину сделать как можно меньше, тем самым создавая результат как можно быстрее. Репертуар, который не нуждается в повторном рендеринге, мешает ему, является ли это "лучшей практикой".