Рубрики
Без рубрики

Изучение API React Reconciler

Подход Tinkerer, чтобы понять внутренние органы реагирования.

Автор оригинала: Manas Jayanth.

Прошло пять лет с момента реагирования было введено в сообщество Frontend. С момента его выпуска он открыл новые возможности для экспресс-пользовательского интерфейса.

С реагированием и связанной с этим экосистемой сообщество постоянно работает над решением недостатков клиентских сценариев.

Одним из таких усилий было реагировать волокно, что позволило разработчикам приложений США просто объявить, как выглядит наш код и как оно должно вести себя в зависимости от изменений данных, а за кулисами он вычисляет необходимые изменения в UI.

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

Мало известно, Видимо О том, о пакете, который отделяет вычисление от планирования самого обновлений и может помочь нам написать наших собственных пользовательских рендереров. На самом деле, React-Exconiver, рассматриваемый пакет, открыл все новые возможности. Были предприняты попытки обеспечить работу React Code в Оборудование на основе фирмы , PDFS , QML , Regl , Фрамер анимации Отказ

Сегодня давайте нацеленным на саму Дом и напишем нашего собственного крошечного React-Dom рендерер. Если вы относитесь к этому достаточно серьезно, возможно, вы можете заменить реакцию с ним в приложениях и увидеть прибыль в размерах пучков и Perf. Давайте погрузиться в.

Во-первых, давайте загрузить реактивный проект, используя Create React App.

npx create-react-app my-react-dom-project
# Or if you use yarn
yarn create react-app my-react-dom-project

Давайте изменим компонент корневого приложения для чего-то проще, по причинам, которые скоро станут очевидными.

import React, { Component } from 'react';

class App extends Component {
  constructor() {
    super();
    this.state = {
      count: 0
    };
  }
  render() {
    const onClickHandler = () => {
      this.setState(state => {
        return {
          count: state.count + 1
        };
      });
    };
    return (
      
{this.state.count}
); } } export default App;

Бег мельницы React Code правильно? Если вы не знакомы ни с чем здесь, рекомендуется сильнее, чтобы мы получили наши основные основы реагирования. Также, пожалуйста, игнорируйте искренние проблемы сейчас в интересах разборчивости.

Далее создайте пустой файл для нашего пользовательского рендерера DOM и вызовите его renderer.js.

export default {}

И импортировать нашу renderer.js вместо реагирования в index.js

import React from 'react';
import ReactDOM from './renderer.js';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(, document.getElementById('root'));
registerServiceWorker();

Ошибка, верно?

1_0rt0yf9ijf7imyyypocw2tw.png.png.

Итак, что мы точно знаем, что нам нужно от нашего рендерера – представляют метод? Способ, который принимает наши элементы реагирования, узел контейнера DOM и обратный вызов, верно? Мы будем вручить реагировать наши элементы, узел контейнера, обратный вызов и реагирование делает это магическими и запускает обратный вызов для сигнала, который он сделал свою работу. С React-Exconilers мы можем контролировать некоторые из этой магии.

Хорошая новость заключается в том, что официальная реактивная реакция использует пакет React-Ecconilers. Мы можем оттуда оттуда.

Мы находим, что Reactreconier (функция, экспортированная по пакету)

  1. Нужен конфиг.
  2. Нуждается в внутренней структуре данных контейнера для рендера в
  3. Запустите UpdateContainer с нашими элементами, внутренняя форма контейнера и обратного вызова мы намереваемся быть запущенным после рендеринга.

Итак, давайте отдам это выстрел.

import ReactReconciler from 'react-reconciler';

const hostConfig = {};
const DOMRenderer = ReactReconciler(hostConfig);

let internalContainerStructure;
export default {
  render(elements, containerNode, callback) {

    // We must do this only once
    if (!internalContainerStructure) {
      internalContainerStructure = DOMRenderer.createContainer(
        containerNode,
        false,
        false
      );
    }

    DOMRenderer.updateContainer(elements, internalContainerStructure, null, callback);
  }
}

И у нас есть следующая ошибка.

1_cahzkyuimrtzfcmhjulvoa.png.

Ведь наш hostconfig это пустой объект!

Обратите внимание, что все ссылки на «HOST», в коде и в прозе, относятся к среде, будет запущен код RACT. Не секрет, что React работает на Mobile (Android/iOS) – React просто нуждается в среде JS, которая может рендерировать обновления пользовательских пользователей. В нашем проекте хост является сама дома.

Первые вещи сначала – что сейчас? Internall React нуждается в способе отслеживания времени для вещей, таких как выяснение, если кусок вычислений «истек истек» или вырвало отведенное время. Проверьте Обсуждение Лин Кларка на реактивном волокне для более глубокого погружения в тему. Но тогда одна чудеса “не может реагировать на использование чего-то вроде даты. Теперь?” Может! Но приводные среды могут обеспечить лучшие способы отслеживания времени. Как Производительность. Теперь ? Все хозяины могут быть не идентичны в отношении этого. В любом случае, давайте использовать Дата. Теперь на данный момент и двигаться дальше.

Хороший способ выяснить, что идет в HostConfig, будет посмотреть на ReactomHostConfig.js (на момент написания). Мы найдем, что нам нужно следующее.

const hostConfig = {
  getRootHostContext(rootContainerInstance) {
  },

  getChildHostContext(parentHostContext, type, rootContainerInstance) {
  },

  getPublicInstance(instance) {
  },

  prepareForCommit(containerInfo) {
  },

  resetAfterCommit(containerInfo) {
  },

  createInstance(
    type,
    props,
    rootContainerInstance,
    hostContext,
    internalInstanceHandle
  ) {
  },

  appendInitialChild(parentInstance, child) {
  },

  finalizeInitialChildren(
    domElement,
    type,
    props,
    rootContainerInstance,
    hostContext
  ) {
  },

  prepareUpdate(
    domElement,
    type,
    oldProps,
    newProps,
    rootContainerInstance,
    hostContext
  ) {
  },

  shouldSetTextContent(type, props) {
  },

  shouldDeprioritizeSubtree(type, props) {
  },

  createTextInstance(
    text,
    rootContainerInstance,
    hostContext,
    internalInstanceHandle
  ) {
  },

  now: null,

  isPrimaryRenderer: true,
  scheduleDeferredCallback: "",
  cancelDeferredCallback: "",

  // -------------------
  //     Mutation
  // -------------------

  supportsMutation: true,

  commitMount(domElement, type, newProps, internalInstanceHandle) {
  },

  commitUpdate(
    domElement,
    updatePayload,
    type,
    oldProps,
    newProps,
    internalInstanceHandle
  ) {
  },

  resetTextContent(domElement) {
  },

  commitTextUpdate(textInstance, oldText, newText) {
  },

  appendChild(parentInstance, child) {
  },

  appendChildToContainer(container, child) {
  },

  insertBefore(parentInstance, child, beforeChild) {
  },

  insertInContainerBefore(container, child, beforeChild) {
  },

  removeChild(parentInstance, child) {
  },

  removeChildFromContainer(container, child) {
  }
};
import ReactReconciler from 'react-reconciler';

const hostConfig = {
  getRootHostContext(rootContainerInstance) {
    return {}
  },

  getChildHostContext(parentHostContext, type, rootContainerInstance) {
    return {};
  },

  getPublicInstance(instance) {
    console.log('getPublicInstance');
  },

  prepareForCommit(containerInfo) {
  },

  resetAfterCommit(containerInfo) {
  },

  createInstance(
    type,
    props,
    rootContainerInstance,
    hostContext,
    internalInstanceHandle
  ) {
    return document.createElement(type);
  },

  appendInitialChild(parentInstance, child) {
    parentInstance.appendChild(child)
  },

  finalizeInitialChildren(
    domElement,
    type,
    props,
    rootContainerInstance,
    hostContext
  ) {
    const { children, ...otherProps } = props;
    Object.keys(otherProps).forEach(attr => {
      if (attr === 'className') {
        domElement.class = otherProps[attr];
      } else if (attr === 'onClick') {
        const listener = otherProps[attr];
        if (domElement.__ourVeryHackCacheOfEventListeners) {
          domElement.__ourVeryHackCacheOfEventListeners.push(listener)
        } else {
          domElement.__ourVeryHackCacheOfEventListeners = [ listener ]
        }
        domElement.addEventListener('click', listener);
      } else {
        throw new Error('TODO: We haven\'t handled other properties/attributes')
      }
    })
  },

  prepareUpdate(
    domElement,
    type,
    oldProps,
    newProps,
    rootContainerInstance,
    hostContext
  ) {
    console.log('prepareUpdate');

    return [ null ];

  },

  shouldSetTextContent(type, props) {
    return false;
  },

  shouldDeprioritizeSubtree(type, props) {
    console.log('shouldDeprioritizeSubtree');
  },

  createTextInstance(
    text,
    rootContainerInstance,
    hostContext,
    internalInstanceHandle
  ) {
    return document.createTextNode(text);
  },

  now: Date.now,

  isPrimaryRenderer: true,
  scheduleDeferredCallback: "",
  cancelDeferredCallback: "",

  // -------------------
  //     Mutation
  // -------------------

  supportsMutation: true,

  commitMount(domElement, type, newProps, internalInstanceHandle) {
    console.log('commitMount');
  },

  commitUpdate(
    domElement,
    updatePayload,
    type,
    oldProps,
    newProps,
    internalInstanceHandle
  ) {
  },

  resetTextContent(domElement) {
  },

  commitTextUpdate(textInstance, oldText, newText) {
    textInstance.nodeValue = newText;
  },

  appendChild(parentInstance, child) {
  },

  appendChildToContainer(container, child) {
    container.appendChild(child)
  },

  insertBefore(parentInstance, child, beforeChild) {
    console.log('insertBefore');
  },

  insertInContainerBefore(container, child, beforeChild) {
    console.log('insertInContainerBefore');
  },

  removeChild(parentInstance, child) {
    console.log('removeChild');
  },

  removeChildFromContainer(container, child) {
    console.log('removeChildFromContainer');
  }
};


const DOMRenderer = ReactReconciler(hostConfig);

let internalContainerStructure;
export default {
  render(elements, containerNode, callback) {

    // We must do this only once
    if (!internalContainerStructure) {
      internalContainerStructure = DOMRenderer.createContainer(
        containerNode,
        false,
        false
      );
    }

    DOMRenderer.updateContainer(elements, internalContainerStructure, null, callback);
  }
}

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

1_1kyfkztkxfgix6qnqwuyxa.png.png.

getrothostContext и getchildhostcontext Этот бит требует немного чтения и покинуть. Сравнение визуализации на официальном репо, мы можем безопасно сделать это: React Reconciler предоставляет GetrothostContext и GetChildHostContext в качестве способов поделиться некоторыми контекстами между другими функциями конфигурации. Подробнее об этом, надеюсь, в последующем пост. На данный момент давайте вернемся пустые объекты.

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

CreateTextInstance Давайте регистрируем все аргументы, полученные в функции.

1_kk3dfs9otwb90vozbnmurq.png.

Вот что они есть,

0 => Исходное значение this.state.count

=> Контейнер DOM

{tag: 6, ключ: null, тип: null, statenode: null, return: fibernode, …} => Связанное волокно. У нас нет беспокойства об этом. Большую часть времени нам просто нужно передать его на примирен. Это считается непрозрачным. В нашем простом рендере нам даже не нужно это делать. Реагирование звонков CreateTextInstance Для создания текстовых узлов в хост-среде. Так что просто верните document.createTextnode (текст)

createTextInstance(
    text,
    rootContainerInstance,
    hostContext,
    internalInstanceHandle
) {

    return document.createTextNode(text);
}

CreateInStance Снова на регистрацию всех аргументов, которые мы видим

Тип => Тип узла DOM. Например, Div, Span, P и др. Реквизируются => Просматриваются реквизиты. Поскольку мы никогда не проходили никому в Div в нашем приложении, прошло только дети.

=> корневой контейнер {…} => Узел волокна, аналогичный createTextnode, давайте просто вернемся с узел div i.e. => document.createelement (тип)

createInstance(
    type,
    props,
    rootContainerInstance,
    hostContext,
    internalInstanceHandle
) {
    return document.createElement(type);
},

AppeDinitialChild.

1_jdxubqpjy4wmxeaf9padrq.png.

Мы видим родительский узел DOM, без набора атрибутов или свойств и его соответствующим дочерним узлом. Только это имеет смысл добавить ребенка к родителю, используя API нашего хозяина, в нашем случае .appendChild ()

appendInitialChild(parentInstance, child) {
    parentInstance.appendChild(child)
}

финарицинициалхильники Это аргументы

  1. Доменение:
    from
    {this.state.count}
    в нашем app.js
  2. Тип: Div
  3. Реквизит {дети: 0} от {this.state.count} и это начальное значение 0
  4. rootcontainerInstance.
  5. HostContext.

Если мы посмотрим ближе к журналам, мы находим, что OnlySetextContent => CreateTextInstance => CreateEtance => FinalizeInitialChildrens происходит для каждого элемента, объявленного в App.js –

{this.state.count}
<кнопка onclick = {onclickhandler}> Увеличение Родительский div:

Прикончитель пытается создать узлы в хост-среде, в нашем случае DOM!

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

1_mubgwwt03ymv4xr5s5efza.png.

Это представляет

{this.state.count}

1_ciejpjtbu1acvfoz2kunkg.png.

И это, <КНОПКА ONCLICK = {ONCLICKHANDLER}> Увеличение

1_pmidd6zvjcqdqv14z1i9aa.png.

Обратите внимание, как на элементах нет классов. FinalizeInitialChildren можно использовать для применения классов, обработчиков событий и других атрибутов и свойств. Поскольку в нашем простом приложении все, что нам действительно нужно, это классы (классы HTML) и обработчики событий, давайте применим их. Если вы хотите обрабатывать другие атрибуты и свойства, это будет место.

  finalizeInitialChildren(
    domElement,
    type,
    props,
    rootContainerInstance,
    hostContext
  ) {
    const { children, ...otherProps } = props;
    Object.keys(otherProps).forEach(attr => {
      if (attr === 'className') {
        domElement.class = otherProps[attr];
      } else if (attr === 'onClick') {
        const listener = otherProps[attr];
        if (domElement.__ourVeryHackCacheOfEventListeners) {
          domElement.__ourVeryHackCacheOfEventListeners.push(listener)
        } else {
          domElement.__ourVeryHackCacheOfEventListeners = [ listener ]
        }
        domElement.addEventListener('click', listener);
      } else {
        throw new Error('TODO: We haven\'t handled other properties/attributes')
      }
    })
  },

PrepareForCommit и ResetAfterCommit Позвольте покинуть эти NOOP на данный момент. Но иначе, если вы думаете, прежде чем примиритель вводит фазу фиксации, вам нужно что-нибудь сделать, сделайте это в Prepareforcommit Отказ Аналогично, чистое после этого можно сделать в RESETAFTERCOMMIT.

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

appendChildToContainer(container, child) {
  container.appendChild(child)
},

Вуаля!

1_ийнxbxelgmnidchxo-wv8w.png.

Наш первый визуализация с использованием API React-Exconier!

Нажмите на увеличение пару раз.

1_znij67sr47igjvyadg9gea.png.

Ui не обновляется. Несколько методов в конфигурации Config Reconiler.

Мы уже видели RESETAFTERCOMMIT Отказ Его pepareupdate и CommunitePupdate Мы после. Хотя мы не видим Cudyupdate Лучше всего мы смотрим на это сейчас, потому что он очень тесно работает с pepareupdate как CommunitePupdate Отказ

Помните, как первоначально реагировал в мир штормом с этой вещью под названием «Дифференциальный алгоритм». Ну, мы можем написать это сейчас сейчас!

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

Поскольку мы определяем примирение (различие), мы также можем выбрать, какую структуру данных удерживает изменения, и именно эта структура данных, которая передается вокруг – возвращена нами из pepareupdate , переданный на Cudyupdate Отказ CommunitePupdate довольно просто – нет необходимости в какой-либо структуре данных, проходящей вокруг. Все, что нам действительно нужно, это старый текст и новый обновленный и посмотреть, нужно ли обращаться к лицу. В большинстве простых случаев вы можете просто назначить новый текст в элемент DOM.

commitTextUpdate(textInstance, oldText, newText) {
  textInstance.nodeValue = newText;
},

Давайте получим наше приложение на работу сейчас (особенно для тех из нас, кто ищет чувство выполненного долга) и Revisit Cudyupdate и pepareupdate позже.

import ReactReconciler from 'react-reconciler';

const hostConfig = {
  getRootHostContext(rootContainerInstance) {
    return {}
  },

  getChildHostContext(parentHostContext, type, rootContainerInstance) {
    return {};
  },

  getPublicInstance(instance) {
    console.log('getPublicInstance');
  },

  prepareForCommit(containerInfo) {
  },

  resetAfterCommit(containerInfo) {
  },

  createInstance(
    type,
    props,
    rootContainerInstance,
    hostContext,
    internalInstanceHandle
  ) {
    return document.createElement(type);
  },

  appendInitialChild(parentInstance, child) {
    parentInstance.appendChild(child)
  },

  finalizeInitialChildren(
    domElement,
    type,
    props,
    rootContainerInstance,
    hostContext
  ) {
    const { children, ...otherProps } = props;
    Object.keys(otherProps).forEach(attr => {
      if (attr === 'className') {
        domElement.class = otherProps[attr];
      } else if (attr === 'onClick') {
        const listener = otherProps[attr];
        if (domElement.__ourVeryHackCacheOfEventListeners) {
          domElement.__ourVeryHackCacheOfEventListeners.push(listener)
        } else {
          domElement.__ourVeryHackCacheOfEventListeners = [ listener ]
        }
        domElement.addEventListener('click', listener);
      } else {
        throw new Error('TODO: We haven\'t handled other properties/attributes')
      }
    })
  },

  prepareUpdate(
    domElement,
    type,
    oldProps,
    newProps,
    rootContainerInstance,
    hostContext
  ) {
    console.log('prepareUpdate');

    return [ null ];

  },

  shouldSetTextContent(type, props) {
    return false;
  },

  shouldDeprioritizeSubtree(type, props) {
    console.log('shouldDeprioritizeSubtree');
  },

  createTextInstance(
    text,
    rootContainerInstance,
    hostContext,
    internalInstanceHandle
  ) {
    return document.createTextNode(text);
  },

  now: Date.now,

  isPrimaryRenderer: true,
  scheduleDeferredCallback: "",
  cancelDeferredCallback: "",

  // -------------------
  //     Mutation
  // -------------------

  supportsMutation: true,

  commitMount(domElement, type, newProps, internalInstanceHandle) {
    console.log('commitMount');
  },

  commitUpdate(
    domElement,
    updatePayload,
    type,
    oldProps,
    newProps,
    internalInstanceHandle
  ) {
  },

  resetTextContent(domElement) {
  },

  commitTextUpdate(textInstance, oldText, newText) {
    textInstance.nodeValue = newText;
  },

  appendChild(parentInstance, child) {
  },

  appendChildToContainer(container, child) {
    container.appendChild(child)
  },

  insertBefore(parentInstance, child, beforeChild) {
    console.log('insertBefore');
  },

  insertInContainerBefore(container, child, beforeChild) {
    console.log('insertInContainerBefore');
  },

  removeChild(parentInstance, child) {
    console.log('removeChild');
  },

  removeChildFromContainer(container, child) {
    console.log('removeChildFromContainer');
  }
};


const DOMRenderer = ReactReconciler(hostConfig);

let internalContainerStructure;
export default {
  render(elements, containerNode, callback) {

    // We must do this only once
    if (!internalContainerStructure) {
      internalContainerStructure = DOMRenderer.createContainer(
        containerNode,
        false,
        false
      );
    }

    DOMRenderer.updateContainer(elements, internalContainerStructure, null, callback);
  }
}

Давайте вернемся к pepareupdate и Cudyupdate Отказ

Нам нужно немного изменять наше приложение – это слишком просто, чтобы спустить pepareupdate и Cudyupdate Отказ

Допустим, мы хотим отобразить счетчик красным, как только он превышает порогов, скажем, 10. Наш App.js должен быть обновлен со следующей строкой

5 ? "counter red": "counter" }> {this.state.count}

И давайте предположим, что файл CSS (App.csss) имеет некоторые стили, чтобы изменить цвет текста.

.red {
  color: red;
}

Сохранить, перезагрузите и начните увеличивать счетчик.

1_rdgzvivivafsvpt8un3tcmxw.png.

Обратите внимание, как pepareupdate и Cudyupdate вызвать три раза каждый. Это один для div.root , внутреннее Div и кнопка Отказ Вы можете быстро убедиться, что, временно добавляя случайную разметку, как

Некоторый текст

import React, { Component } from 'react';

class App extends Component {
  constructor() {
    super();
    this.state = {
      count: 0
    };
  }
  render() {
    const onClickHandler = () => {
      this.setState(state => {
        return {
          count: state.count + 1
        };
      });
    };
    return (
      
{ /* Add

some text

temporarily and see prepareUpdate and commitUpdate get called 5 times instead of 3 */ }
{this.state.count}
); } } export default App;

Давайте попробуем развать наш дом! Давайте изучим все его аргументы.

prepareUpdate(
    domElement,
    type,
    oldProps,
    newProps,
    rootContainerInstance,
    hostContext
) {
    console.log('prepareUpdate', oldProps, newProps, rootContainerInstance);
    return [ null ];
},
1_awkzdvqxuq2snytlpe2j7a.png.

Как нажал на увеличение, дети обновлены от 0 до 1. Нажав на увеличение> 5 раз мы видим,

1_ivmg9hjdvlxxjee5j8nuxg.png.

На 6-м клике клавиши ClassName изменилось с «счетчика» на «счетчик красных». Еще один опоры изменился.

В идеальном мире, если структуры DOM Data в земле JS имели простое прямое сопоставление к структурам данных C ++ под капотом, мы могли бы слепо и запустить операцию обновления на каждом из этих реквизитов. Но мы работаем в ограничении! Дом связан с маршалскими структурами взад и вперед. Различие – это необходимый взлом, а не функция для написания функциональных декларативных интерфейсов UI Отказ Итак, давайте вычисляем наименьшее количество необходимых обновлений, которые нам нужно сделать до DOM.

  prepareUpdate(
    domElement,
    type,
    oldProps,
    newProps,
    rootContainerInstance,
    hostContext
  ) {
    const propKeys = new Set(
      Object.keys(newProps).concat(
        Object.keys(oldProps)
      )
    ).values();
    const payload = [];
    for (let key of propKeys) {
      if (
        key !== 'children' && // text children are already handled
        oldProps[key] !== newProps[key]
      ) {
        payload.push({ [key]: newProps[key] })
      }
    }
    return payload;
  },

И применять эти обновления в приверженность

  commitUpdate(
    domElement,
    updatePayload,
    type,
    oldProps,
    newProps,
    internalInstanceHandle
  ) {
    updatePayload.forEach(update => {
      Object.keys(update).forEach(key => {
        if (key === 'onClick') {
          domElement.__ourVeryHackyCacheOfEventListeners.forEach(listener => { // To prevent leak
            domElement.removeEventListener('click', listener)
          })
          domElement.__ourVeryHackyCacheOfEventListeners = [ update[key] ];
          domElement.addEventListener('click', update[key])
        } else {
          domElement[key] = update[key];
        }
      })
    })
  },

Обратите внимание, что мы обрабатываем слушатели событий (которые также передаются как реквизиты) по-разному. И для простоты мы гарантируем только один слушатель событий в любое время.

И там мы идем!

1_mbfqvlfqu0zesw3fx4q8gg.gif.

Полный рендерс.js.

import ReactReconciler from 'react-reconciler';

const hostConfig = {
  getRootHostContext(rootContainerInstance) {
    return {}
  },

  getChildHostContext(parentHostContext, type, rootContainerInstance) {
    return {};
  },

  getPublicInstance(instance) {
    console.log('getPublicInstance');
  },

  prepareForCommit(containerInfo) {
    // console.log('prepareForCommit');
  },

  resetAfterCommit(containerInfo) {
    // console.log('resetAfterCommit');
  },

  createInstance(
    type,
    props,
    rootContainerInstance,
    hostContext,
    internalInstanceHandle
  ) {
    // console.log(
    //   'createInstance',
    //   type,
    //   props,
    //   rootContainerInstance,
    //   hostContext,
    // );
    return document.createElement(type);
  },

  appendInitialChild(parentInstance, child) {
    parentInstance.appendChild(child)
  },

  finalizeInitialChildren(
    domElement,
    type,
    props,
    rootContainerInstance,
    hostContext
  ) {
    const { children, ...otherProps } = props;
    Object.keys(otherProps).forEach(attr => {
      if (attr === 'className') {
        domElement.className = otherProps[attr];
      } else if (attr === 'onClick') {
        const listener = otherProps[attr];
        if (domElement.__ourVeryHackyCacheOfEventListeners) {
          domElement.__ourVeryHackyCacheOfEventListeners.push(listener)
        } else {
          domElement.__ourVeryHackyCacheOfEventListeners = [ listener ]
        }
        domElement.addEventListener('click', listener);
      } else {
        throw new Error('TODO: We haven\'t handled other properties/attributes')
      }
    })
    // console.log('finalizeInitialChildren', domElement, type, props, rootContainerInstance, hostContext);
  },

  prepareUpdate(
    domElement,
    type,
    oldProps,
    newProps,
    rootContainerInstance,
    hostContext
  ) {
    const propKeys = new Set(
      Object.keys(newProps).concat(
        Object.keys(oldProps)
      )
    ).values();
    const payload = [];
    for (let key of propKeys) {
      if (
        key !== 'children' && // text children are already handled
        oldProps[key] !== newProps[key]
      ) {
        payload.push({ [key]: newProps[key] })
      }
    }
    return payload;
  },

  shouldSetTextContent(type, props) {
    return false; // || true;
  },

  shouldDeprioritizeSubtree(type, props) {
    console.log('shouldDeprioritizeSubtree');
  },

  createTextInstance(
    text,
    rootContainerInstance,
    hostContext,
    internalInstanceHandle
  ) {
    // console.log(
    //   'createTextInstance',
    //   text,
    //   rootContainerInstance,
    //   hostContext,
    // );
    return document.createTextNode(text);
  },

  now: Date.now,

  isPrimaryRenderer: true,
  scheduleDeferredCallback: "",
  cancelDeferredCallback: "",

  // -------------------
  //     Mutation
  // -------------------

  supportsMutation: true,

  commitMount(domElement, type, newProps, internalInstanceHandle) {
    console.log('commitMount');
  },

  commitUpdate(
    domElement,
    updatePayload,
    type,
    oldProps,
    newProps,
    internalInstanceHandle
  ) {
    updatePayload.forEach(update => {
      Object.keys(update).forEach(key => {
        if (key === 'onClick') {
          domElement.__ourVeryHackyCacheOfEventListeners.forEach(listener => { // To prevent leak
            domElement.removeEventListener('click', listener)
          })
          domElement.__ourVeryHackyCacheOfEventListeners = [ update[key] ];
          domElement.addEventListener('click', update[key])
        } else {
          domElement[key] = update[key];
        }
      })
    })
  },

  resetTextContent(domElement) {
    console.log('resetTextContent');
  },

  commitTextUpdate(textInstance, oldText, newText) {
    // console.log('commitTextUpdate', oldText, newText);
    textInstance.nodeValue = newText;
  },

  appendChild(parentInstance, child) {
    console.log('appendChild');
  },

  appendChildToContainer(container, child) {
    // console.log('appendChildToContainer', container, child);
    container.appendChild(child)
  },

  insertBefore(parentInstance, child, beforeChild) {
    console.log('insertBefore');
  },

  insertInContainerBefore(container, child, beforeChild) {
    console.log('insertInContainerBefore');
  },

  removeChild(parentInstance, child) {
    console.log('removeChild');
  },

  removeChildFromContainer(container, child) {
    console.log('removeChildFromContainer');
  }
};


const DOMRenderer = ReactReconciler(hostConfig);

let internalContainerStructure;
export default {
  render(elements, containerNode, callback) {

    // We must do this only once
    if (!internalContainerStructure) {
      internalContainerStructure = DOMRenderer.createContainer(
        containerNode,
        false,
        false
      );
    }

    DOMRenderer.updateContainer(elements, internalContainerStructure, null, callback);
  }
}

Надеюсь, вам понравилась эта статья и продолжила пособие с помощью React-Exconiver!

GitHub Repo: https://github.com/preetheansacrifice/my-react-dom.