[Перевод] Виртуальный DOM и алгоритм diff реагируют, как он работает?

React.js

Переводчик: Кайт
Автор: Гетил Джордж Куриан.
Оригинальная ссылка:medium.com/@self-employed имеют два или…

Статья устарела, на основе реакции v16 скоро будет выпущена, эта статья только для справки

Я пытался понять глубоко и ясноVirtual-DOMкак это работает, и искал источники, которые более подробно объясняют детали того, как это работает.

Поскольку я не получил ни одной полезной информации в своих обширных поисках, я, наконец, решил изучитьreactа такжеreact-domисходный код, чтобы лучше понять, как они работают.

Но прежде чем мы начнем, вы подумали о том, почему мы не рендерим напрямуюDOMОбновить?

В следующем разделе я представлюDOMкак он был создан и почемуReactсоздан с самого началаVirtual-DOM

DOMкак он был создан

(Изображение через Mozilla — https://developer.mozilla.org/en-US/docs/Introduction_to_Layout_in_Mozilla)

я не буду много говорить оDOMсоздается и отображается на экране, но см.здесьа такжездесьпонять всеHTMLПеревести вDOMи шаги для рисования на экране.

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

Что такое Virtual-DOM?Перекомпоновать/перерисоватьшаги для повышения производительности в больших и сложных проектах.

В следующем разделе будет рассказано больше оVirtual-DOMПодробности о том, как это работает.

пониматьVirtual-DOM

Теперь, когда вы понимаетеDOMКак он устроен, так что давайте узнаем об этом больше сейчасVirtual-DOMБар.

Здесь я сначала воспользуюсь небольшим приложением, чтобы объяснитьvirtual domКак это работает, чтобы вы могли легко увидеть, как это работает.

Я не буду вдаваться в подробности того, как работает первоначальный рендеринг, просто сосредоточусь на том, что происходит при повторном рендеринге, это поможет вам понятьvirtual domа такжеdiffКак работает алгоритм, как только вы понимаете процесс, понимание начального рендеринга становится простым :).

можно найти в этомgit repoНайдите исходный код этого приложения. Этот простой интерфейс калькулятора длинный:

КромеMain.jsа такжеCalculator.jsКроме этого, другие файлы в этом репо можно игнорировать.

// Calculator.js
import React from "react"
import ReactDOM from "react-dom"

export default class Calculator extends React.Component{
	constructor(props) {
		super(props);
		this.state = {output: ""};
	}

	render(){
		let IntegerA,IntegerB,IntegerC;
		

		return(
			<div className="container">						
				<h2>using React</h2>
				<div>Input 1: 
					<input type="text" placeholder="Input 1" ref="input1"></input>
				</div>
				<div>Input 2 :
					<input type="text" placeholder="Input 2" ref="input2"></input>
				</div>
				<div>
					<button id="add" onClick={ () => {
						IntegerA = parseInt(ReactDOM.findDOMNode(this.refs.input1).value)
						IntegerB = parseInt(ReactDOM.findDOMNode(this.refs.input2).value)
						IntegerC = IntegerA+IntegerB
						this.setState({output:IntegerC})
					  }
					}>Add</button>
					
					<button id="subtract" onClick={ () => {
						IntegerA = parseInt(ReactDOM.findDOMNode(this.refs.input1).value)
						IntegerB = parseInt(ReactDOM.findDOMNode(this.refs.input2).value)
						IntegerC = IntegerA-IntegerB
						this.setState({output:IntegerC})

					  }
					}>Subtract</button>
				</div>
				<div>
					<hr/>
					<h2>Output: {this.state.output}</h2>
				</div>
				
			</div>
		);
	}
}
// Main.js
import React from "react";
import Calculator from "./Calculator"

export default class Layout extends React.Component{
	render(){	

		return(
			<div>
			        <h1>Basic Calculator</h1>
				 <Calculator/>
			</div>
		);
	}
}

генерируется при начальной загрузкеDOMЭто выглядит так:

(DOM после первоначального рендеринга)

Вот структура приведенного выше дерева DOM, построенного React внутри:

Теперь добавьте два числа и нажмите кнопку «Добавить» для более глубокого понимания.

чтобы понятьDiffкак работает алгоритм иreconciliationКак запланироватьvirtual-domк реальномуDOMДа, в этом калькуляторе я ввожу 100 и 50 и нажимаю кнопку «Добавить», ожидая, что будет выведено 150:

输入1: 100
输入2: 50

输出: 150

Так что же происходит, когда вы нажимаете кнопку «Добавить»?

В нашем примере при нажатии кнопки «Добавить» мы устанавливаем состояние с выходным значением 150:

// Calculator.js
 <button id="add" onClick={() => {
        IntegerA = parseInt(ReactDOM.findDOMNode(this.refs.input1).value);
        IntegerB = parseInt(ReactDOM.findDOMNode(this.refs.input2).value);
        IntegerC = IntegerA+IntegerB;
        this.setState({output:IntegerC});
      }}>Add</button>

компонент тега

(Примечание: компоненты, которые будут изменены)

Во-первых, давайте разберемся с первым шагом, как маркируется компонент:

  1. всеDOMСлушатели событий завернуты вReactВ пользовательском прослушивателе событий, поэтому при нажатии кнопки «Добавить» это событие щелчка отправляется прослушивателю событий реакции, который выполняет анонимную функцию, которую вы видите в приведенном выше коде.

  2. В анонимной функции мы вызываемthis.setStateМетод получает новое значение состояния.

  3. этоsetState()Метод будет похож на следующие строки кода, помечающие компоненты по очереди.

// ReactUpdates.js  - enqueueUpdate(component) function
dirtyComponents.push(component);

Вам интересно, почему react помечает не кнопку, а весь компонент? Ну, это потому, что ты использовалthis.setState()звонитьsetStateметод, и это это относится к этомуCalculatorкомпоненты

  1. Итак, теперь нашCalculatorКомпонент помечен, посмотрим, что будет дальше.

Пройти жизненный цикл компонента

отлично! Теперь, когда компонент помечен, что происходит дальше? следующее обновлениеvirtual dom, затем используйтеdiffАлгоритм, чтобы сделатьreconciliationи обновить настоящийDOM

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

Вот нашиCalculatorкомпонент вreactвыглядит как в:

Calculator Wrapper

Вот шаги для обновления этого компонента:

  1. Это черезreactобновляется путем запуска пакетного обновления;

  2. При пакетном обновлении он проверяет, помечен ли компонент, а затем запускает обновление.

 //ReactUpdates.js
var flushBatchedUpdates = function () {
  while (dirtyComponents.length || asapEnqueued) {
    if (dirtyComponents.length) {
      var transaction = ReactUpdatesFlushTransaction.getPooled();
      transaction.perform(runBatchedUpdates, null, transaction);
  1. Затем он проверяет, есть ли состояние ожидания, которое необходимо обновить, или проблемаforceUpdate.
if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
      this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);

В нашем примере вы можете увидетьthis._pendingStateQueueв оболочке калькулятора с новым состоянием вывода

  1. Во-первых, он проверяет, используем ли мыcomponentWillReceiveProps(), если использовать полученноеpropsвозобновитьstate.

  2. Следующий,reactпроверит, используем ли мы его в компонентеshouldComponentUpdate(), если мы используем, мы можем проверить, нужен ли компонентуstateилиpropsизменения и повторный рендеринг.

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

  1. Следующие шаги по очередиcomponentWillUpdate(), render(), Ну наконец тоcomponentDidUpdate()

Начиная с шагов 4, 5 и 6, мы используем толькоrender()

  1. Теперь давайте посмотрим глубжеrender()Что произошло за это время?

рендерингVirtual-DOMСравните различия и перестройте

Компоненты рендеринга — обновлениеVirtual-DOM, бегатьdiffалгоритм и обновление до реальногоDOMсередина

В нашем примере все элементы в этом компоненте будут вVirtual-DOMперестроен в

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

 var prevRenderedElement = this._renderedComponent._currentElement;
 //Calculator.render() method is called and the element is build.
 var nextRenderedElement = this._instance.render(); 

Важным моментом является то, что здесь находится вызывающий компонентrenderместо метода. Например,Calculator.render()

этоreconciliationПроцесс обычно состоит из следующих шагов:

Метод рендеринга компонента — обновляет виртуальный DOM, запускает алгоритм сравнения и, наконец, обновляет DOM.

Красная пунктирная линия означает всеreconciliationШаги будут повторяться для следующего дочернего узла и дочерних узлов внутри дочернего узла.

Приведенная выше блок-схема суммируетVirtual DOMкак обновляется фактический DOM.

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

Так что вы можете увидеть это в нашем примереreconciliationКак это работает:

я пропускаю предыдущий<div>изreconciliation, направлять вас, чтобы увидетьDOMсталиOutput:150шаги обновления,

  • Reconciliationиз класса этого компонента с именем "контейнер"<div>Начинать
  • Его дочерний элемент - это вывод, содержащий<div>, следовательно,reactначнется с этого дочернего узлаreconciliation
  • Теперь у этого дочернего узла есть дочерние узлы<hr>а также<h2>
  • такreactбудет<hr>воплощать в жизньreconciliation
  • Далее он начнется с<h2>изreconciliationзапускается, так как имеет собственные дочерние узлы, выходной иstateвывод, он начнет работать на обоихreconciliation
  • Первый выходной текст проходитreconciliation, так как это ничего не меняет, поэтомуDOMНичего не нужно менять.
  • Далее, изstateВыход проходит черезreconciliation, так как теперь у нас есть новое значение, 150,reactобновит настоящийDOM. ...

реальностьDOMоказание

В нашем примере вreconciliationЗа это время изменились только поля вывода, как показано ниже, а рисунок мерцает в консоли разработчика.

перерисовывать только вывод

и в реалеDOMОбновлено дерево компонентов на

В заключение

Заключение Хотя этот пример очень прост, он даст вам базовое пониманиеreactчто происходит внутри.

Я не стал выбирать более сложное приложение, потому что рисовать все дерево компонентов очень утомительно. :-|

reconciliationпроцессReact

  • Сравните предыдущий внутренний экземпляр со следующим внутренним экземпляром
  • обновить внутренний экземплярVirtual DOM(JavaScriptобъект) в структуре дерева компонентов.
  • Обновляйте только реальные изменения узлов и их дочерних элементов, которые имеют реальные изменения.DOM.

(Примечание:в авторскомreactверсияv15.4.1)