Устройство кодовой базы

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

Если вы хотите внести свой вклад в React, то мы надеемся, что эта глава вам поможет.

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

Top-Level Folders

После клонирования репозитория React вы увидите папки верхнего уровня:

  • packages содержит метаданные (такие как package.json) и исходный код (подпапка src) для каждого пакета из репозитория React. Большая часть работы с кодом происходит в подпапках src внутри каждого пакета.
  • fixtures содержит несколько небольших React-приложений для контрибьютеров.
  • build содержит скомпилированную версию React. Этого каталога нет в репозитории, но он появится после того, как вы соберёте проект в первый раз.

Документация расположена в отдельном репозитории.

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

Тесты

У нас нет отдельной верхнеуровневой папки с юнит тестами. Вместо этого, мы помещаем их в папку __tests__, расположенную рядом с тестируемыми файлами.

Например, тесты для setInnerHTML.js расположены в __tests__/setInnerHTML-test.js.

Предупреждения и инварианты

Для работы с предупреждениями в React используется модуль warning.

var warning = require('warning');

warning(
  2 + 2 === 4,
  'Математика сегодня не работает.'
);

Предупреждения будут показаны, когда условие внутри warning равно false.

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

Использование дополнительной переменной поможет избежать спама в консоль.

var warning = require('warning');

var didWarnAboutMath = false;
if (!didWarnAboutMath) {
  warning(
    2 + 2 === 4,
    'Математика сегодня не работает.'
  );
  didWarnAboutMath = true;
}

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

var invariant = require('invariant');

invariant(
  2 + 2 === 4,
  'Ты не пройдёшь!'
);

Если условие внутри invariant равно false, тогда выбрасывается исключение.

«Инварианты» — это всего лишь способ сказать: «Условие всегда истинно». Вы можете думать о них как об утверждениях (assertion).

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

Режимы разработки и продакшена

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

На этапе компиляции добавится проверка process.env.NODE_ENV !== 'production', определяющая, должен ли данный блок кода присутствовать в сборке CommonJS.

Выражение будет равно true в неминифицированной сборке, а в минифицированной будет вырезан весь блок вместе с if.

if (__DEV__) {
  // Этот код будет выполнен только в режиме разработки.
}

Flow

Недавно мы представили статический анализатор Flow. Файлы, помеченные аннотацией @flow в заголовке после лицензии, будут подвергнуты проверке типов.

Мы принимаем пулл реквесты на дополнение аннотаций Flow в уже существующий код. Вот пример кода:

ReactRef.detachRefs = function(
  instance: ReactInstance,
  element: ReactElement | string | number | null | false,
): void {
  // ...
}

Новый код, если это возможно, должен использовать Flow. Вы можете выполнить yarn flow, чтобы произвести проверку типов.

Динамические инъекции

React использует динамические инъекции в некоторых модулях. Несмотря на то, что они явные, это всё ещё неудачное решение, потому что затрудняет понимание кода. Причина их существования в том, что изначально React поддерживал только DOM. React Native появился как форк React. Мы должны добавлять динамические инъекции, чтобы позволить React Native переопределять некоторое поведение.

Ниже вы можете увидеть, как в модулях объявляются динамические зависимости:

// Инжектится динамически
var textComponentClass = null;

// Зависит от инжектируемого значения
function createInstanceForText(text) {
  return new textComponentClass(text);
}

var ReactHostComponent = {
  createInstanceForText,

  // Позволяет использовать динамические инъекции
  injection: {
    injectTextComponentClass: function(componentClass) {
      textComponentClass = componentClass;
    },
  },
};

module.exports = ReactHostComponent;

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

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

Множество пакетов

React является монолитным репозиторием. Он содержит множество отдельных пакетов, чтобы изменения были согласованными, а проблемы решались в одном месте.

Ядро React

Ядро включает в себя весь верхнеуровневый API, например:

  • React.createElement()
  • React.Component
  • React.Children

Ядро включает в себя только API необходимый для объявления компонентов. Оно не включает алгоритм согласования или какой-либо платформо-специфический код. Этот код находится в компонентах React DOM и React Native.

Код ядра расположен в папке packages/react. Он доступен в npm в виде пакета react. Соответствующая сборка для браузера экспортирует глобальную переменную React и называется react.js.

Рендереры

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

Рендереры превращают React дерево в платформо-специфический код.

Они расположены в каталоге packages/:

  • React DOM Renderer рендерит React-компоненты в DOM. Он реализует ReactDOM API и доступен как пакет react-dom из npm репозитория. Можно подключать как отдельный бандл react-dom.js, экспортирующий глобальную переменную ReactDOM.
  • React Native Renderer рендерит React компоненты в нативные представления. Используется внутри React Native.
  • React Test Renderer рендерит React компоненты в JSON-дерево. Используется при тестировании снимками через фреймворк Jest и доступен как пакет react-test-renderer в npm.

Мы начали поддерживать единственный неофициальный рендерер react-art, который раньше находился в отдельном GitHub-репозитории.

Примечание:

Технически react-native-renderer — это очень тонкий слой, который учит React взаимодействовать с React Native. Платформо-специфический код, управляющий нативными представлениями, расположен в репозитории React Native вместе с его компонентами.

Согласователи

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

В качестве решения, различные рендереры имеют между собой общий код. В React мы называем эту часть «согласователь». Когда запланировано такое обновление, как setState(), согласователь вызывает render() в дереве компонентов и монтирует, обновляет либо размонтирует их.

Согласователи не являются отдельнымы пакетами, потому что не имеют открытого API. Вместо этого они используются исключительно такими рендерерами как React DOM и React Native.

Согласователь Stack

Согласователь «Stack» — это реализация, которая использовалась в React 15 и более ранних версиях. Мы перестали его использовать, но задокументировали в следующией главе.

Согласователь Fiber

В согласователе «Fiber» мы пытаемся исправить давно существующие ошибки и решить проблемы, появившиеся в согласователе Stack.

Его основными целями являются:

  • Разделение прерываемых задач на подзадачи.
  • Дать задачам приоритеты, иметь возможность перемещать их и переиспользовать.
  • Иметь возможность перемещаться вперёд и назад между родителями и детьми в разметке React.
  • Иметь возможность возвращать множество элементов из метода render().
  • Улучшенная обработка ошибок.

Вы можете узнать больше об архитектуре React Fiber здесь и здесь. Несмотря на то, что он включён в React 16, асинхронная функциональность по умолчанию не включена.

Исходный код расположен в папке packages/react-reconciler.

Система событий

В React реализована система синтетических событий, которая не зависит от используемого рендерера, и работает одинаково как в React DOM, так и в React Native. Исходный код расположен в каталоге packages/react-events.

Больше подробностей вы можете узнать из видео (66 mins).

Что дальше?

В следующей главе вы познакомитесь с реализацией согласователя (версий React 15 и ранее) более подробно. Для нового согласователя мы ещё не писали документацию.