Як написати ваш власний віртуальний dom - devschacht - medium

Переклад статьіdeathmood: How to write your own Virtual DOM

Є дві речі, які вам необхідно знати для написання вашого власного віртуального DOM. Вам навіть не потрібно занурюватися в вихідний код React або в вихідний код будь-який інший імплементації віртуального DOM. Вони такі великі і комплексні, але в дійсності основна частина віртуального DOM може бути написана менше ніж за 50 рядків коду. 50. Строк. Коду.

Тут закладено дві концепції:

  • Віртуальний DOM - будь подання цього DOM
  • Коли ми щось змінюємо в нашому віртуальному DOM дереві, ми створюємо нове віртуальне дерево. Алгоритм порівнює ці два дерева (старе і нове), знаходить різницю і вносить тільки необхідні мінімальні зміни в справжній DOM, щоб він відповідав віртуальному.

Це все! Давайте заглибимося в кожну з цих концепцій.

Подання нашого DOM дерева

Добре, в першу чергу нам потрібно якось зберігати наше DOM дерево в пам'яті. І ми можемо зробити це за допомогою простих JS об'єктів. Припустимо, у нас є це дерево:

Виглядає просто, чи не так? Як ми можемо це уявити за допомогою простих JS об'єктів:

Тут ви можете помітити дві речі:

  • Ми представляємо DOM елементи в об'єктом вигляді
  • Ми описуємо текстові ноди простими JS рядками

Але писати великі дерева таким способом досить складно. Так давайте напишемо допоміжну функцію, щоб нам стало простіше розуміти структуру:

Тепер ми можемо описувати наше DOM дерево в такому вигляді:

Виглядає набагато чистіше, чи не так? Але ми навіть можемо піти далі. Ви ж чули про JSX, чи не так? Так, я хотів би застосувати його ідеї тут. Так, як же він працює?

Якщо ви читали офіційну документацію Babel до JSX тут. ви знаєте, що Babel транспілірует цей код:

в щось на зразок цього:

Добре, насправді вона повідомляє Babel: «Ей, скомпілюйте цей jsx, але замість React.createElement. підстав h ». Тут ви можете підставити що завгодно замість h. І це буде скомпілювати.

Отже, ми будемо писати наш DOM таким чином:

І це буде скомпілювати Babel в такий код:

Виконання функції h поверне простий JS об'єкт - наше уявлення віртуального DOM.

Спробуйте самі на JSFiddle (не забудьте вказати Babel в якості мови).

Застосування нашого уявлення DOM

Добре, тепер ми маємо уявлення нашого DOM дерева в JS об'єкті, з нашої власної структурою. Це круто, але нам потрібно якось створити справжній DOM з нього. Звичайно, ми не можемо просто застосувати наше уявлення до DOM.

Перш за все давайте встановимо деякі припущення і визначимося з термінологією:

  • Я буду писати все змінні, що зберігають справжні DOM Ноди (елементи, текстові ноди), починаючи з $. таким чином $ parent буде справжнім DOM елементом
  • Віртуальне DOM уявлення буде зберігатися у змінній з ім'ям node
  • Як і в React, у вас може бути тільки одна коренева нода, всі інші Ноди будуть всередині

Тепер, коли ми з усім цим розібралися, давайте напишемо функцію createElement (...), яка візьме віртуальну DOM ноду і поверне справжню DOM ноду. Забудьте поки про props і children. ми займемося ними пізніше:

Таким чином, ми можемо передавати в неї як віртуальні текстові ноди, так і Ноди віртуальних елементів, і це буде працювати.

Тепер давайте подумаємо про дітей: кожен з них також є текстової нодою або елементом. Тому вони також можуть бути створені за допомогою нашої функції createElement (...). Ви відчуваєте це? Пахне рекурсією :)) Отже, ми можемо викликати createElement (...) для кожного з дочірніх елементів, а потім appendChild () їх в наш елемент наступним чином:

Вау, виглядає відмінно. Давайте поки залишимо осторонь props Ноди. Ми поговоримо про них пізніше. Вони не потрібні нам для базового розуміння концепцій віртуального DOM і тільки додадуть складності.

Давайте підемо далі і спробую це в JSFiddle.

Обробка змін

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

Як порівняти дерева? Нам необхідно обробити такі ситуації:

  • Якщо в певному місці немає старої Ноди: нода була додана і нам необхідно використовувати appendChild (...)
  • Ні нової Ноди в певному місці: ця нода була видалена і нам потрібно використовувати removeChild (...)
  • Інша нода в цьому місці: нода змінилася і нам потрібно використовувати replaceChild (...)
  • Ноди ті ж: нам потрібно йти глибше і порівнювати дочірні Ноди

Добре, давайте напишемо функцію updateElement (...), що приймає три параметри: $ parent, newNode і oldNode. $ Parent - батьківський елемент реального DOM нашій віртуальній Ноди. Зараз ми бачимо, як обробити всі ситуації, описані вище.

Якщо немає старої Ноди

Якщо немає нової Ноди

Тут у нас з'являється проблема: якщо немає Ноди в певному місці в новому віртуальному дереві, ми повинні видалити її з реального DOM. Але як це зробити? Ми знаємо батьківський елемент (він передається в функцію) і тому ми повинні викликати $ parent.removeChild (...) і передавати посилання на реальний елемент DOM. Але у нас його немає. Якби ми знали становище нашої Ноди в батьку, ми могли б отримати його посилання за допомогою $ parent.childNodes [index], де index - позиція нашої Ноди в батьківському елементі.

Добре, давайте припустимо, що цей index буде передаватися нашої функції (і це дійсно станеться, як ви побачите пізніше), тоді наш код буде таким:

Нода змінилася

Перш за все нам необхідно написати функцію, що порівнює дві Ноди і що повідомляє нам, якщо вузол дійсно змінився. Ми повинні враховувати, що це можуть бути як елементи, так і текстові ноди:

І тепер, маючи index поточної Ноди батька, ми можемо легко замінити її на новостворену ноду:

Порівняння дочірніх елементів

І останнє, але не менш важливе: ми повинні пройти кожен дочірній елемент на обох нодах, порівнювати їх і викликати updateElement (...) для кожного з них. Так, знову рекурсія.

Але є кілька речей, які потребують розгляду перед написанням коду:

  • Ми повинні порівнювати дітей, якщо нода є елементом (текстові ноди не можуть мати дітей)
  • Зараз ми передаємо посилання на поточну ноду як на батька
  • Ми повинні порівняти всіх дітей один за одним, навіть якщо в якийсь момент ми отримаємо undefined (це нормально, наша функція вміє обробляти це)
  • І, нарешті, index - це просто покажчик на дочірню ноду в масиві children

Збираємо всі разом

Так це воно. Ми добралися. Я помістив весь код в JSFiddle і реалізація дійсно становить 50 LOC, як я вам і обіцяв. Можете піти і пограти з ним.

Відкрийте інструменти розробника і подивіться як відбуваються зміни при натисканні кнопки Reload.

висновок

Вітаю! Ми зробили це. Ми написали свою реалізацію віртуального DOM. І він працює. Я сподіваюся, що прочитавши цю статтю, ви зрозуміли основні концепції і те, як віртуальний DOM повинен працювати і як React працює під капотом.

  • Установка атрибутів елементів і їх порівняння і оновлення
  • Додавання обробників подій для наших елементів
  • Можливість нашого віртуального DOM працювати з компонентами, як React
  • Отримання посилань на реальні DOM Ноди
  • Використання віртуального DOM з бібліотеками, які безпосередньо мутують справжній DOM: jQuery і її плагіни
  • І багато іншого…