Об алгоритме react diff (перевод)

внешний интерфейс алгоритм JavaScript React.js

React — это библиотека классов js, разработанная facebook для создания пользовательских интерфейсов, предназначенных для повышения производительности. В этой статье я расскажу об алгоритме сравнения в React и его механизме рендеринга, чтобы вы могли лучше оптимизировать свою программу.

Алгоритм сравнения

Прежде чем углубляться в детали реализации, важно понять, как работает React.

var MyComponent = React.createClass({ 
    render: function() { 
        if (this.props.first) { 
            return <div className="first"><span>A Span</span></div>; 
        } else { 
            return <div className="second"><p>A Paragraph</p></div>; 
        } 
    } 
});

В любой момент вы можете описать пользовательский интерфейс так, как хотите. Вы должны понимать, что результат, который он отображает, не является настоящим DOM. Визуализированные результаты — это просто легковесные объекты JavaScript. Мы называем это виртуальным DOM.
React будет использовать эту нотацию, чтобы попытаться найти минимальное количество шагов от предыдущего рендеринга до следующего, например, если бы мы использовали<MyComponent first={false} />заменить<MyComponent first={true} />, вставьте настоящий DOM, затем удалите его, вот результат директивы DOM:

из ничего
Создайте узел DOM:<div className="first"><span>A Span</span></div>

от одного до двух
использоватьclassName="second"заменить атрибутыclassName="first"
использовать<p>A Paragraph</p>заменить узел<span>A Span</span>

От двух до ни одного
Удалить узел:<div className="second"><p>A Paragraph</p></div>

пошаговое сравнение

Нахождение наименьшего количества модификаций между двумя произвольными деревьями требует n3 выполнений, что, как вы понимаете, для нас неприемлемо. React использует простой и на данный момент очень мощный метод вычисления, чтобы найти их изменения, а количество выполнений всего n раз.

React сравнивает различия двух деревьев узлов уровень за уровнем, что значительно снижает сложность, а потеря точности не велика, потому что очень редко можно сравнить движение разных уровней дерева компонентов в веб-приложении. Компоненты обычно можно перемещать только в боковом направлении в подкомпонентах.

Список

Предположим, у нас есть компонент, который рендерит 5 компонентов за одну итерацию и вставляет новый компонент в середину списка компонентов при следующем рендеринге. Просто с этой информацией действительно трудно понять, как сопоставить два списка компонентов.

По умолчанию React связывает первый компонент предыдущего списка с первым компонентом следующего списка и так далее. Вы можете предоставить свойство Key, чтобы помочь React найти их сопоставление. На практике обычно легко выбрать из них только что вставленный компонент.

компоненты

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

Например, если<Header>одеяло<ExampleBlock>заменить, React удалит<Header>и создать<ExampleBlock>. Нам не нужно тратить драгоценное время, пытаясь сопоставить два компонента, которые вряд ли будут похожи.

делегация мероприятия

Присоединение прослушивателей событий к узлам DOM мучительно медленно и потребляет много памяти. Однако React реализует популярную технику под названием «делегирование событий». React пошел еще дальше и повторно реализовал систему событий, совместимую с W3C. Это означает, что проблемы совместимости с обработкой событий Internet Explorer 8 уйдут в прошлое, и все имена событий будут одинаковыми в разных браузерах.

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

Вместо этого каждый компонент React имеет уникальный идентификатор, который представляет его иерархию. Мы можем использовать простые манипуляции со строками, чтобы получить идентификаторы всех родительских узлов. Мы обнаружили, что хранить прослушиватели событий в хэш-карте лучше, чем прикреплять их к виртуальному DOM.

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

// dispatchEvent('click', 'a.b.c', event) 
clickCaptureListeners['a'](event); 
clickCaptureListeners['a.b'](event); 
clickCaptureListeners['a.b.c'](event); 
clickBubbleListeners['a.b.c'](event);
clickBubbleListeners['a.b'](event); 
clickBubbleListeners['a'](event);

Браузер создает новый объект события для каждого события и слушателя. У этого объекта есть очень хорошее свойство: вы можете сохранить ссылку на объект события и даже изменить его. Однако это означает выделение большого количества памяти. React изначально выделяет пул памяти для этих объектов. Всякий раз, когда требуется объект события, он будет повторно использоваться из пула памяти, что значительно сокращает сбор мусора в памяти.

оказывать

Пакетная обработка:

Всякий раз, когда вы вызываете setState для компонента, React помечает его как грязный. В конце цикла обработки событий React просматривает все «грязные» компоненты и перерисовывает их. В случае пакетной обработки это означает, что DOM рендеринга обновляется ровно один раз во время цикла событий. Это свойство является ключом к повышению производительности приложения, но его очень трудно получить при написании обычного JavaScript. В приложениях React он доступен по умолчанию.

Рендеринг поддерева:

При вызове setState компонент перестраивает виртуальный DOM своих дочерних элементов. Если вы вызовете setState для корневого элемента, все приложение React будет перерисовано. Все компоненты, даже если они не изменились, используют свой метод рендеринга. Это может звучать пугающе и неэффективно, но на практике это прекрасно работает, поскольку мы не касаемся реального DOM.
Во-первых, давайте поговорим об отображении пользовательского интерфейса. Из-за ограниченного места на экране вы обычно одновременно отображаете от сотен до тысяч элементов последовательно. JavaScript достаточно быстр для обработки бизнес-логики и всего управления интерфейсом.
Еще один важный момент: при написании кода React вы обычно не вызываете setState на корневом узле каждый раз, когда происходит изменение. Вы можете вызвать его для компонента, который получает изменение события, или для родительского компонента. Мы редко выходим наверх, обычно взаимодействие с пользователем происходит на соответствующих изменениях компонента.

Выберите рендеринг поддерева:

Наконец, вы можете запретить повторный рендеринг некоторых поддеревьев. Если вы используете следующий метод для компонента:
boolean shouldComponentUpdate(object nextProps, object nextState)
Основываясь на предыдущем и следующем свойствах/состоянии компонента, вы можете сказать React, что компонент не изменился и не нуждается в повторном рендеринге. Правильное использование этого метода может значительно повысить производительность. Чтобы использовать его, вам нужно сравнивать объекты JavaScript. Здесь все еще много проблем, например, является ли сравнение js-объектов глубоким или поверхностным, и если сравнение глубокое, я использую неизменяемую структуру данных или делаю глубокую копию. И вы также должны иметь в виду, что эта функция будет вызываться всегда, поэтому вы хотите убедиться, что время вычисления меньше, чем время, необходимое для рендеринга компонента, даже если рендеринг избыточен.

 到底什么情况下使用shouldComponentUpdate?
    按照React团队的说法,shouldComponentUpdate是保证性能的紧急出口  
    http://jamesknelson.com/should-i-use-shouldcomponentupdate
    http://www.infoq.com/cn/news/2016/07/react-shouldComponentUpdate

В заключение

В технологии, делающей React такой быстрой, нет ничего нового, и мы давно знаем, что манипуляции с DOM обходятся дорого, поэтому вам следует выполнять массовое чтение и запись в DOM, использовать делегирование событий — все это делает вашу программу более удобной. Быстрее.
Люди до сих пор говорят о React, потому что тот факт, что эти оптимизации сложно реализовать с помощью обычного кода Javascript, а React делает это по умолчанию, — вот что выделяет React.
Модель стоимости производительности React также проста для понимания: все поддерево перерисовывается каждый раз, когда вы устанавливаетеState. Если вы хотите повысить производительность, вызывайте setState как можно реже и используйте shouldComponentUpdate, чтобы предотвратить переделывание всех поддеревьев.

Оригинальный адрес:calendar.perfplanet.com/2013/diff/