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

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

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

Я делаю то, что не является оптимальным? Я всегда могу обновить метку count вручную (и состояние молча), и это будет почти мгновенная операция, но это отнимает точку использования 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>

ссылка на jsFiddle, на всякий случай: http://jsfiddle.net/9nrnz1qm/3/

3 ответов


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

  • используйте производственную сборку
  • выполнить 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;
},

обновленный скрипку

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


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

    <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-ключи для получения дополнительной информации.

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

    <input onClick={this.updateCompletedState} type="checkbox" checked={this.state.data.completed ? 'checked' : ''} />
    
  3. вы делаете некоторую конкатенацию строк, чтобы установить правильный className. Для более читаемого кода попробуйте использовать nice and simple реагировать.дополнения.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;
    }
    

Я смотрю, где вы render () список...

<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 и id такой:

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

затем просто повторите один TodoItem, как он изменен, например:

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

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