[Углубленное реагирование] Углубленный анализ принципа рендеринга и характеристик виртуального DOM.

React.js
[Углубленное реагирование] Углубленный анализ принципа рендеринга и характеристик виртуального DOM.

Управляемое чтение

ReactвиртуальныйDOMиDiffАлгоритмReactОчень важные основные функции этой части исходного кода также очень сложны.Понимание принципов этой части знаний поможет вам более глубоко освоить.Reactочень нужно.

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

Эта статья начинается с исходного кода и анализирует виртуальныйDOMОсновной принцип рендеринга (первый рендеринг) иReactОчки оптимизации производительности для него.

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

Общие проблемы в разработке

  • Почему нужно цитироватьReact
  • индивидуальныеReactПочему компоненты должны быть капитализированы
  • Reactкак предотвратитьXSS
  • ReactизDiffАлгоритмы и прочееDiffЧем отличаются алгоритмы
  • keyсуществуетReactроль в
  • Как написать высокую производительностьReactкомпоненты

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

Во-первых, давайте посмотрим, что такое виртуальныйDOM:

виртуальный DOM

image

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

иReactсначала преобразует ваш код вJavaScriptобъект, то этоJavaScriptЗатем объекты превращаются в реальныеDOM. этоJavaScriptобъекты называются виртуальнымиDOM.

Например, следующий абзацhtmlКод:

<div class="title">
      <span>Hello ConardLi</span>
      <ul>
        <li>苹果</li>
        <li>橘子</li>
      </ul>
</div>

существуетReactможет храниться как таковойJSКод:


const VitrualDom = {
  type: 'div',
  props: { class: 'title' },
  children: [
    {
      type: 'span',
      children: 'Hello ConardLi'
    },
    {
      type: 'ul',
      children: [
        { type: 'li', children: '苹果' },
        { type: 'li', children: '橘子' }
      ]
    }
  ]
}

Когда нам нужно создать или обновить элементы,ReactСначала сделайте этоVitrualDomобъекты создаются и изменены, а затемVitrualDomРендеринг объекта в реальныйDOM;

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

Зачем использовать виртуальный DOM

ReactЗачем использоватьVitrualDomКак насчет этого плана?

Повышение эффективности разработки

использоватьJavaScript, при написании приложения мы фокусируемся на том, как обновитьDOM.

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

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

Об улучшении производительности

Во многих статьях говоритсяVitrualDomМожет улучшить производительность, это утверждение на самом деле очень однобоко.

Прямое управлениеDOMБез сомнения, это очень требовательно к производительности. ноReactиспользоватьVitrualDomТакже не обойтись без операцииDOMиз.

Если это первый рендер,VitrualDomНет никакого преимущества, даже если он выполняет больше вычислений и потребляет больше памяти.

VitrualDomПреимущество в том, чтоReactизDiffАлгоритмы и стратегии пакетной обработки,ReactПеред обновлением страницы способ обновления и рендеринга рассчитывается заранее.DOM. По сути, этим процессом расчета мы непосредственно оперируемDOMМожно также судить и осознавать это самим, но это обязательно отнимет много сил и времени, а то, что мы делаем сами, зачастую не так хорошо, какReactВ ПОРЯДКЕ. Итак, в этом процессеReactПомогли нам "улучшить производительность".

Так что я больше склоняюсь к тому,VitrualDomПомогает нам повысить эффективность разработки, помогает нам рассчитать, как эффективнее обновлять при повторном рендеринге, а неDOMЭксплуатация быстрее.

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

Кроссбраузерная совместимость

image

Reactна основеVitrualDomЯ реализовал набор собственных механизмов событий, смоделировал процесс всплытия и захвата событий и применил такие методы, как прокси-сервер событий и пакетное обновление, чтобы сгладить проблему совместимости событий различных браузеров.

Кроссплатформенная совместимость

image

VitrualDomзаReact带来了跨平台渲染的能力。 отReact NativeНапример.Reactв соответствии сVitrualDomнарисовать соответствующую платформуuiСлои, но позы, нарисованные на разных платформах, разные.

Принцип реализации виртуального DOM

Если вы не хотите читать сложный исходный код или у вас недостаточно времени сейчас, вы можете пропустить эту главу и перейти сразу👇Принцип виртуального DOM и обзор функций

image

На приведенном выше рисунке мы продолжаем расширяться.Согласно процессу на рисунке мы будем анализировать виртуальныйDOMпринцип реализации.

JSX и createElement

мы реализуемReactВы можете выбрать два метода кодирования при использовании компонентов, первый — использоватьJSXнаписать:

class Hello extends Component {
  render() {
    return <div>Hello ConardLi</div>;
  }
}

Второй – использовать непосредственноReact.createElementнаписать:

class Hello extends Component {
  render() {
    return React.createElement('div', null, `Hello ConardLi`);
  }
}

На самом деле два вышеуказанных способа написания эквивалентны,JSXПросто дляReact.createElement(component, props, ...children)Синтаксический сахар, обеспечиваемый данным способом. То есть всеJSXВ конечном итоге код будет преобразован вReact.createElement(...),BabelПомогли нам в процессе этого преобразования.

как показано нижеJSX

<div>
  <img src="avatar.png" className="profile" />
  <Hello />
</div>;

будетBabelпреобразовать в

React.createElement("div", null, React.createElement("img", {
  src: "avatar.png",
  className: "profile"
}), React.createElement(Hello, null));

Уведомление,babelбудет оцениваться во время компиляцииJSXПервая буква компонента в, когда первая буква строчная, считается нативнойDOMЭтикетка,createElementПервая переменная компилируется в строку, когда первая буква заглавная, считается пользовательским компонентом,createElementПервая переменная компилируется как объект;

Кроме того, посколькуJSXбыть впереди времениBabelкомпилируется, так чтоJSXНевозможно динамически выбрать тип во время выполнения, например следующий код:

function Story(props) {
  // Wrong! JSX type can't be an expression.
  return <components[props.storyType] story={props.story} />;
}

Его нужно записать следующим образом:

function Story(props) {
  // Correct! JSX type can be a capitalized variable.
  const SpecificStory = components[props.storyType];
  return <SpecificStory story={props.story} />;
}

Итак, используйтеJSXвам нужно установитьBabelплагинbabel-plugin-transform-react-jsx

{
    "plugins": [
        ["transform-react-jsx", {
            "pragma": "React.createElement"
        }]
    ]
}

Создать виртуальный DOM

Давайте посмотрим на виртуальныйDOMистинный вид следующегоJSXКод выводится на консоль:

<div className="title">
      <span>Hello ConardLi</span>
      <ul>
        <li>苹果</li>
        <li>橘子</li>
      </ul>
</div>

image

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

image

createElementОперация, выполняемая внутри функции, очень проста,propsПосле обработки с дочерними элементами вернитеReactElementОбъекты, давайте разберем их один за другим:

(1).Обработка реквизита:

image

  • 1. Ставим специальные свойстваref,keyотconfigизвлечь и присвоить
  • 2. Поместите специальные свойстваself,sourceотconfigизвлечь и присвоить
  • 3. Выньте и назначьте другие атрибуты, кроме специальных атрибутов, дляprops

В последующих статьях будет подробно описана роль этих специальных свойств.

(2) Получить дочерние элементы

image

  • 1. Получить количество дочерних элементов - все параметры после второго параметра
  • 2. Если есть только один дочерний элемент, назначьте егоprops.children
  • 3. Если есть несколько дочерних элементов, заполните дочерние элементы массивом и назначьте егоprops.children

(3) Обработка реквизита по умолчанию

image

  • статические свойства компонентаdefaultPropsопределено по умолчаниюpropsсделать задание

ReactElement

ReactElementОбъединить несколько переданных и возвращенных атрибутов.

  • type: Тип элемента, который может быть собственным типом HTML (строка) или пользовательским компонентом (функция илиclass)
  • key: Уникальный идентификатор компонента, используемый дляDiffалгоритм, который будет подробно описан ниже
  • ref: используется для доступа к родномуdomузел
  • props: передается компонентуprops
  • owner: сейчас строитсяComponentпринадлежитComponent

?typeof: свойство, которое мы не часто видим, оно назначается какREACT_ELEMENT_TYPE:

var REACT_ELEMENT_TYPE =
  (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||
  0xeac7;

видимый,?typeofЯвляетсяSymbolПеременная типа, которая предотвращаетXSS.

Если на вашем сервере есть уязвимость, которая позволяет пользователям хранить произвольныеJSONобъект, а клиентский код ожидает строку, что может стать проблемой:

// JSON
let expectedTextButGotJSON = {
  type: 'div',
  props: {
    dangerouslySetInnerHTML: {
      __html: '/* put your exploit here */'
    },
  },
};
let message = { text: expectedTextButGotJSON };
<p>
  {message.text}
</p>

JSONнельзя хранить вSymbolпеременная типа.

ReactElement.isValidElementфункция для определенияReactЯвляется ли компонент действительным, ниже приводится его конкретная реализация.

ReactElement.isValidElement = function (object) {
  return typeof object === 'object' && object !== null && object.?typeof === REACT_ELEMENT_TYPE;
};

видимыйReactбудет отображаться без?typeofИдентификация и компоненты, не прошедшие проверку правил, отфильтровываются.

когда ваша среда не поддерживаетSymbolчас,?typeofназначается как0xeac7, почему,ReactРазработчик дал ответ:

0xeac7немного похожеReact.

self,sourceТолько в непроизводственных средах он будет добавлен к объекту.

  • selfУказывает, какой экземпляр компонента включен в данный момент.
  • _sourceУказывает файл, из которого исходит код отладки (fileName) и строки кода (lineNumber).

Преобразование виртуального DOM в реальный DOM

Выше мы разобрали преобразование кода в виртуальныйDOMпроцесс, давайте посмотрим наReactкак виртуализироватьDOMпреобразовать в реальныйDOM.

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

image

Процесс 1: Начальная обработка параметров

написание нашегоReactПосле компонента нам нужно вызватьReactDOM.render(element, container[, callback])Визуализируйте компонент.

renderФактический вызов внутри функции_renderSubtreeIntoContainer, давайте посмотрим на его конкретную реализацию:

  render: function (nextElement, container, callback) {
    return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
  },

image

  • 1. Использовать текущий компонентTopLevelWrapperобернуть

TopLevelWrapperПросто пустая оболочка, которая обеспечиваетrootIDсвойства, а вrenderКомпонент возвращается в функцию.

TopLevelWrapper.prototype.render = function () {
  return this.props.child;
};

ReactDOM.renderПервый параметр функции может быть нативнымDOMтак же может бытьReactкомпонент, обертывающий слойTopLevelWrapperОни могут быть обработаны единообразно в последующих рендерингах, независимо от того, являются ли они нативными или нет.

  • 2. Определите, был ли элемент визуализирован в корневом узле.Если он был визуализирован, определите, выполнять ли операцию обновления или удаления
  • 3. ОбращениеshouldReuseMarkupпеременная, указывающая, нужно ли перемаркировать элемент
  • 4. Вызов проходит в параметрах, обработанных выше_renderNewRootComponent, вызываемый после завершения рендерингаcallback.

существует_renderNewRootComponentвызыватьinstantiateReactComponentКлассифицируйте и оберните компоненты, которые мы передаем:

image

В зависимости от типа компонента,ReactСледующие четыре категории компонентов создаются на основе исходных компонентов, компоненты классифицируются и визуализируются:

  • ReactDOMEmptyComponent: пустой компонент
  • ReactDOMTextComponent:текст
  • ReactDOMComponent:РоднойDOM
  • ReactCompositeComponent: настроитьReactкомпоненты

Все они имеют следующие три метода:

  • construct: используется для полученияReactElementдля инициализации.
  • mountComponent: используется для созданияReactElementсоответствующая реальностьDOMилиDOMLazyTree.
  • unmountComponent: удалитьDOMУзел, отвязать событие.

В частности, как рендерить, мы анализируем в процессе 3.

Процесс 2: пакетная обработка, вызов транзакции

существует_renderNewRootComponentиспользуется вReactUpdates.batchedUpdatesперечислитьbatchedMountComponentIntoNodeДелайте пакетную обработку.

ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);

существуетbatchedMountComponentIntoNodeв использованииtransaction.performперечислитьmountComponentIntoNodeПусть он вызывается на основе механизма транзакций.

 transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);

Касательно пакетных транзакций, анализ передо мноймеханизм выполнения setStateВступлений больше.

Процесс 3: Генерация html

существуетmountComponentIntoNodeвызов функцииReactReconciler.mountComponentСоздание нативногоDOMузел.

mountComponentВнутри четыре объекта, сгенерированные процессом 1, фактически называютсяmountComponentметод. Первый взгляд наReactDOMComponent:

image

  • 1. Для спец.DOMЭтикетка,propsдля обработки.
  • 2. Создать в соответствии с типом ярлыкаDOMузел.
  • 3. позвонить_updateDOMPropertiesбудетpropsвставить вDOMузел,_updateDOMPropertiesтакже может использоваться дляprops Diff, первый параметр - это последний визуализированныйprops, второй параметр — текущийprops, если первый параметр пустой, он создается впервые.
  • 4. СоздайтеDOMLazyTreeвозражать и звонить_createInitialChildrenВизуализируйте дочерние узлы на нем.

Так почему бы просто не создатьDOMnode вместо того, чтобы создатьDOMLazyTreeШерстяная ткань? Давайте сначала посмотрим_createInitialChildrenЧто вы наделали:

image

Определить текущий узелdangerouslySetInnerHTMLАтрибуты, являются ли дочерние узлы текстовыми, а другие узлы вызываются отдельноDOMLazyTreeизqueueHTML,queueText,queueChild.

image

Его можно найти:DOMLazyTreeна самом деле является объектом-оболочкой,nodeимущество хранит реальноеDOMузел,children,html,textХраните дочерние элементы, узлы html и текстовые узлы отдельно.

Он предоставляет несколько методов для вставки дочерних элементов,htmlа также текстовые узлы, эти вставки являются условными, когдаenableLazyсобственностьtrueкогда эти дети,htmlи текстовый узел будет вставлен вDOMLazyTreeобъект, когда онfalseбудет вставлен в реальныйDOMв узле.

var enableLazy = typeof document !== 'undefined' &&
  typeof document.documentMode === 'number' ||
  typeof navigator !== 'undefined' &&
  typeof navigator.userAgent === 'string' &&
  /\bEdge\/\d/.test(navigator.userAgent);

видимый:enableLazyявляется переменной, текущий браузерIEилиEdgeкогдаtrue.

существуетIE(8-11)иEdgeВ браузере вставка узлов без потомков один за другим намного эффективнее, чем вставка всего сериализованного полного дерева узлов.

такlazyTreeОсновное решение состоит в том, чтобыIE(8-11)иEdgeЭффективность вставки узлов в браузер будет анализироваться в следующем процессе 4: если текущийIEилиEdge, вам нужно вставить рекурсивноDOMLazyTreeКэшированные дочерние узлы, другим браузерам нужно вставить текущий узел только один раз, потому что их дочерние узлы уже отрисованы, не беспокоясь о проблемах с эффективностью.

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

  • иметь дело сprops,contexи другие переменные, вызовите конструктор для создания экземпляра компонента
  • Определите, является ли это компонентом без сохранения состояния, обработайтеstate
  • перечислитьperformInitialMountжизненный цикл, обработка дочерних узлов, получениеmarkup.
  • перечислитьcomponentDidMountжизненный цикл

существуетperformInitialMountфункция, первый вызовcomponentWillMountжизненный цикл, из-за обычаевReactКомпонент не является настоящим DOM, поэтому функция дочернего узла вызывается в функцииmountComponent. Это также рекурсивный процесс, когда все дочерние узлы отрисовываются, возвращаютсяmarkupи позвониcomponentDidMount.

Процесс 4: рендеринг html

существуетmountComponentIntoNodeВызов в функции будет сгенерирован на предыдущем шагеmarkupвставлятьcontainerконтейнер.

При первом рендере_mountImageIntoNodeочиститcontainerвызывается после дочернего узлаDOMLazyTree.insertTreeBefore:

image

определитьfragmentузел или<object>Плагин:

  • Если это два вышеуказанных, сначала вызовитеinsertTreeChildrenВизуализируйте дочерний узел этого узла в текущий узел, а затем вставьте визуализированный узел вhtml

  • Если это другой узел, сначала вставьте узел во вставку вhtml, затем позвонитеinsertTreeChildrenвставить дочерний узел вhtml.

  • Если не в настоящее времяIEилиEdge, вам не нужно рекурсивно вставлять дочерние узлы, достаточно один раз вставить только текущий узел.

image

  • Суждение неIEилиbEdgeВремяreturn
  • какchildrenне нулевой, рекурсивныйinsertTreeBeforeвставить
  • визуализировать HTML-узел
  • отображать текстовый узел

Собственный прокси-сервер событий DOM

о виртуальномDOMМеханизм события , я специально написал статью, если вам интересно, вы можете 👇[Подробно о реакции] Механизм событий React

Принцип виртуального DOM и обзор функций

Процесс рендеринга компонента React

  • использоватьReact.createElementилиJSXнаписатьReactкомпоненты, практически всеJSXВ конечном итоге код будет преобразован вReact.createElement(...),BabelПомогли нам в процессе этого преобразования.

  • createElementфункциональная параkeyиrefи т. д. специальныеpropsобработать и получитьdefaultPropsпо умолчаниюpropsНазначьте назначения и обработайте входящие дочерние узлы и, наконец, создайтеReactElementобъект (так называемый виртуальныйDOM).

  • ReactDOM.renderбудет генерировать виртуальныйDOMРендеринг в указанный контейнер, который принимает пакетную обработку, транзакции и другие механизмы и оптимизирует производительность определенных браузеров и, наконец, преобразует его в реальныйDOM.

Состав виртуального DOM

которыйReactElementelement, наш компонент в конечном итоге будет представлен в следующей структуре:

  • type: Тип элемента, который может быть собственным типом HTML (строка) или пользовательским компонентом (функция илиclass)
  • key: Уникальный идентификатор компонента, используемый дляDiffалгоритм, который будет подробно описан ниже
  • ref: используется для доступа к родномуdomузел
  • props: передается компонентуprops,chidrenдаpropsСвойство, в котором хранятся дочерние узлы текущего компонента, которые могут быть массивом (несколько дочерних узлов) или объектом (только один дочерний узел)
  • owner: сейчас строитсяComponentпринадлежитComponent
  • self: (нерабочая среда) указывает, какой экземпляр компонента в данный момент находится в
  • _source: (нерабочая среда) указывает файл, из которого исходит код отладки (fileName) и строки кода (lineNumber)

Предотвратить XSS

ReactElementобъект имеет другой?typeofсобственность, этоSymbolпеременная типаSymbol.for('react.element'), когда среда не поддерживаетSymbolчас,?typeofназначается как0xeac7.

Эта переменная предотвращаетXSS. Если на вашем сервере есть уязвимость, которая позволяет пользователям хранить произвольныеJSONобъект, а клиентский код ожидает строку, что может представлять опасность для вашего приложения.JSONнельзя хранить вSymbolпеременная типа, аReactбудет отображаться без?typeofИдентифицированные компоненты отфильтровываются.

Пакет и транзакция

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

О пакетной обработке и механизме транзакций в моей предыдущей статье[Подробно реагировать] механизм выполнения setStateЕсть подробное введение.

Целевая оптимизация производительности

существуетIE(8-11)иEdgeВ браузере вставка узлов без потомков один за другим намного эффективнее, чем вставка всего сериализованного полного дерева узлов.

Reactпройти черезlazyTree,существуетIE(8-11)иEdgeВ других браузерах по очереди отображается один узел, а в других браузерах весьDOMСтруктура строится, а затем вставляется в контейнер целиком.

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

Механизм событий виртуального DOM

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

ReactСамостоятельно сконструированный синтетический объект событияSyntheticEvent, который представляет собой встроенную кросс-браузерную оболочку событий. Он имеет тот же интерфейс, что и собственные события браузера, в том числеstopPropagation()иpreventDefault()Подождите, они работают одинаково во всех браузерах. Это сглаживает проблемы совместимости событий в разных браузерах.

Вышеприведенное анализирует только виртуальныйDOMПринцип и процесс первого рендеринга, естественно сюда не входит виртуальныйDOMпровестиDiffПроцесс мы подробно обсудим в следующей статье.

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

Рекомендуемое чтение

конец

Версия в исходном коде этой статьиReactВерсия 15, относительная16В версии будут некоторые неточности, примерно16Изменения версии будут проанализированы отдельно в последующих статьях.

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

Хотите прочитать больше качественных статей, скачайте исходный файл интеллект-карты в статье, прочтите статьюdemoИсходный код, следуй за мнойблог на гитхабе, твоя звезда✨, лайки и внимание - движущая сила моего постоянного творчества!

Рекомендую обратить внимание на мой паблик WeChat [code secret garden], каждый день выкладывать качественные статьи, будем общаться и расти вместе.