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

О сериале

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

Preact - это быстрая альтернатива React объемом 3 КБ с тем же API ES6.

Я не предполагаю, что может быть реализовано какое-либо знакомство с тем, как (P) реакция может быть реализована: в этом суть обучения! В подходящие моменты я также могу ссылаться на кодовую базу React для сравнения, но понимание гораздо более амбициозной реализации React выходит за рамки наших возможностей.

Мы прочитаем код примерно в порядке его внутренних зависимостей, сначала охватив утилиты и общедоступные интерфейсы, а затем углубившись в внутренности, которые от них зависят. Для меня самой удивительной частью чтения исходного кода Preact было обнаружение того, сколько в нем небольших и понятных файлов. И после того, как вы привыкли к этому, столкнулись лицом к лицу с двумя большими сложными файлами, которые делают большую часть работы. Не буду лгать: меня несколько раз напугали, но я постараюсь облегчить сложность.

В сегодняшней первой части мы рассмотрим действительно простые части:

  • Где живет код
  • Служебные функции
  • Интеграция глобальных опций и инструментов разработки
  • Как JSX становится виртуальными узлами

Где живет код

Интерфейс, который Preact реализует из React, хотя и довольно удобен в использовании, довольно сбивает с толку, чтобы думать о его реализации. Многие зависимости и стеки вызовов являются циклическими: компоненты визуализируют виртуальные узлы, которые описывают еще больше компонентов, «визуализация» иногда означает создание виртуальных узлов, а иногда означает фактическое касание DOM, а реализация жизненного цикла Component распределена по нескольким различным модулям.

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

  • Мы опишем желаемую модель DOM с помощью виртуальных узлов DOM. Возможно, эти узлы будут сгенерированы синтаксисом JSX, который компилируется в вызовы вспомогательной функции. Мы рассмотрим реализацию этого шага в последнем разделе этой статьи.
  • Наши представления могут инкапсулировать логику рендеринга с отслеживанием состояния, унаследовав от класса Component. Объявление интерфейса этого класса и биты, которые могут переопределить определенные компоненты, находятся в src/component.js. Но большая часть реализации жизненного цикла живет в src/vdom/component.js. Функциональным компонентам не нужен собственный интерфейс, но есть некоторый код реализации в src/vdom/functional-component.js. (Если вы не знаете, чем отличаются функциональные компоненты от других, не волнуйтесь: мы поговорим об этом в следующей статье этой серии.)
  • Preact эффективно визуализирует изменения, сопоставляя виртуальные узлы dom с существующей структурой DOM. Большая часть этой логики обрабатывается в src/vdom/diff.js. Когда приходит время фактически сбросить изменения в DOM, он использует помощников из src/dom/index.js. Чтобы ускорить рендеринг, он перерабатывает как виртуальные узлы DOM, так и фактические узлы DOM.
  • И по пути будет ряд других небольших файлов с вспомогательными функциями.

Постарайтесь помнить об этом плане. Теперь мы перейдем к чтению реального кода с помощью самой простой, но наиболее часто импортируемой части Preact.

Служебные функции

Файл src/util.js определяет вспомогательные функции, необходимые где-нибудь в кодовой базе. Определение их локально может показаться странным. В кодовой базе приложения вы можете просто импортировать эти помощники из такого пакета, как lodash. Однако для библиотеки наличие собственных версий означает, что мы можем отказаться от обработки крайних случаев, которые нам не важны, не беспокоиться о зависимостях и сделать наш код как можно меньше.

Я рекомендую вам прочитать файл целиком. Вы можете узнать многие функции, например extend и isFunction. О других стоит помнить, потому что мы встретимся с ними позже:

  • delve получает вложенные свойства: delve({a: {b: 2}}, 'a.b') == 2.
  • hashToClassName генерирует строковые классы DOM из классов сопоставления объектов, независимо от того, присутствуют они или нет: hashToClassName({a: true, b: false, c: true}) === "a c".
  • defer замалчивает различия в браузерах, пытаясь как можно быстрее запланировать асинхронный запуск чего-либо.

Вот так: мы прошли через наш первый настоящий файл в исходном коде Preact, и пока ничего плохого. Это все, что касается служебных функций Preact.

Интеграция глобальных опций и инструментов разработки

Файл src/options.js предоставляет глобальные параметры Preact. Лишь некоторые из этих параметров полезны для обычных приложений, но они возникают несколько раз при реализации библиотеки, особенно при интеграции с инструментами разработки.

Какие глобальные параметры поддерживает Preact?

  • syncComponentUpdates определяет, будут ли дочерние компоненты повторно отображаться сразу после изменения свойств, полученных от их родительских компонентов. По умолчанию это true, что также является поведением по умолчанию в React. Установив для параметра значение false, вы можете отложить выполнение обновлений асинхронно по расписанию, которое вы определяете. Это особенно полезно, если вы делаете анимацию, когда хотите обновлять каждого дочернего элемента один раз за кадр, вместо того, чтобы, возможно, увязнуть в нескольких обновлениях для каждого дочернего элемента в одном кадре. Вы можете увидеть разницу в действии на этом примере анимированного фрактального дерева. (Но будьте осторожны: асинхронный рендеринг может привести к некоторому странному поведению, потому что обновления от родителя к потомку происходят менее предсказуемо. Вам нужно обдумать это, прежде чем использовать его для обычных пользовательских интерфейсов.)
  • Не задокументировано в этом файле, но полезно, если вы хотите асинхронный рендеринг, - это debounceRendering, который вы можете установить для функции планирования, которой вы хотите управлять рендерингом. Если не установлено, асинхронный рендеринг будет запланирован с использованием служебной функции defer. Если вы играете с асинхронным рендерингом, возможно, вам нужно requestAnimationFrame.
  • vnode() - это функция обратного вызова, которая будет вызываться с каждым новым виртуальным узлом, созданным Preact. afterMount(), afterUpdate() и beforeUnmount() - это обратные вызовы, которым передаются компоненты в ожидаемое время.

Эти обратные вызовы глобальных параметров не предназначены для использования в коде приложения. Они были бы просто кошмаром для обслуживания и тестирования. Но они очень полезны для внутреннего взаимодействия Preact с React Devtools, расширением браузера, которое позволяет вам проверять работающее приложение React.

React Devtools работают за счет глубокой интеграции с внутренними компонентами React, такими как форма компонентов, интерфейс согласователя и т. Д. Вместо воссоздания Devtools для Preact, интеграция в devtools / devtools.js работает путем реализации фасадов для частей Реагируйте на то, что Devtools нужно. Точные детали этого кода выходят за рамки этой серии, так как в основном он имеет отношение к внутреннему устройству React. Но я хочу указать на код инициализации внизу, который использует обратные вызовы глобальных параметров для таких вещей, как нормализация виртуальных узлов Preact в ожидаемую форму и для уведомления интерфейса об изменениях в визуализированных компонентах.

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

Убрав часто импортируемые утилиты и параметры, давайте рассмотрим первый и самый простой шаг по фактическому отображению объектов на экране:

Как JSX становится виртуальными узлами

Наши компоненты должны возвращать виртуальные узлы для описания модели DOM, которую они хотят визуализировать. Хотя это не обязательно, синтаксис JSX для создания виртуальных деревьев DOM довольно удобен и широко используется. Поскольку браузеры не понимают JSX, между ним и реальным виртуальным деревом DOM в памяти есть два шага:

  1. Наш код скомпилирован до простого JavaScript, где JSX заменяется вызовами функции для создания виртуальных узлов DOM (или «vnodes»).
  2. Во время выполнения вызовы функций создают дерево виртуальных узлов, описывающих HTML-код, который мы хотим отобразить.

Компиляция JSX до простого JavaScript чаще всего выполняется с помощью Babel. Чтобы увидеть, как это работает, вот пример кода до и после компиляции JSX Babel:

Обратите внимание, что наш JSX с угловой скобкой был превращен в вызовы функций в форме:

h(nodeName, attributes, …children)

По умолчанию Babel предполагает, что JSX будет интерпретироваться React, поэтому он вызывает функцию с именемReact.createElement. В нашем коде Preact такой функции не будет. Поместив прагму /** @jsx h **/ в начало файла (или настроив ее глобально в нашем процессе сборки), мы можем изменить имя используемой функции. Функция построения vnode в Preact называется h, поэтому мы и используем ее здесь.

Но что такое vnode? Его определение in src/vnode.js довольно простое: vnode должен иметь nodeName, который является либо строкой (для обычных элементов html), либо функцией, представляющей компонент. При желании он также может иметь объект attributes, массив children и key.

Файл src/h.js Preact экспортирует функцию для создания виртуальных узлов, которая выглядит сложной, но на самом деле довольно проста. Легче понять, если я перепишу его двумя частями: во-первых, общая структура файла; во-вторых, обработка особых случаев для обработки дочерних узлов vnode:

Обратите внимание на несколько особых случаев обращения с детьми:

  • children можно передать как дополнительные аргументы в h или они могут быть переданы в attributes, переданном h. В последнем случае ключ children удаляется из attributes, поэтому он также не отображается как атрибут DOM.
  • true, false и ложные значения пропускаются. Это полезно для написания условий короткого замыкания JSX, таких как { not_logged_in && "You aren't logged in"}, который оценивается как false, когда вы действительно просто не хотите, чтобы ничего не отображалось.
  • Если смежные дочерние элементы являются строками (или числами, которые приводятся к строкам), они объединяются в одну строку.

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

И это все, что нужно для того, чтобы JSX стал виртуальным dom-узлом. Просто для обзора:

  • Babel компилирует JSX для вызовов функций. При работе с preact нам нужно настроить его для вызова функции h.
  • Функция h возвращает vnode, чьи дочерние элементы были преобразованы в вещи, которые мы можем визуализировать в dom, будь то строки или несколько vnodes.
  • Vnode - это просто описание dom-узла: имя элемента или компонента, возможно, с атрибутами и дочерними элементами.

Следите за новостями для будущих платежей

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

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