Как устроен ReactJS. Пакет React

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


В этой серии статей мы почитаем код и попробуем разобраться за что отвечают пакеты, которые лежат у реакта под капотом, для чего они используются и как они работают. Самые основные, которые мы используем в браузере, – это react, react-dom, events и react-reconciler.


Будем двигаться по порядку и сегодня у нас статья про пакет react. Кому интересно, что же есть в этом пакете, – заходите под кат.


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


function App() {
    const [text, changeText] = React.useState('Initial');

    return (
        <div className="app">
            <span>{text}</span>
            <input
                type="text"
                value={text}
                onInput={(e) => changeText(e.target.value)}
            />
        </div>
    );
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
) ;

Давайте разберём быстренько этот кусок кода. Здесь мы видим вызов хука через React.useState('Initial'), немного JSX и вызов метода render, чтобы всё это попало на страницу.
На самом деле, как многие знают, это не финальный код, который обрабатывает браузер. Перед тем, как он попадёт на выполнение, он транспайлится, например, бабелем. В этом случае то, что возвращает функция, превратится в следующее:


return React.createElement(
    "div",
    {
        className: "app"
    },
    React.createElement("span", null, text),
    React.createElement("input", {
        type: "text",
        value: text,
        onInput: function onInput(e) {
            return changeText(e.target.value);
        }
    })
);

Кому интересно поэкспериментировать и посмотреть, во что превращает ваш код бабель – babel repl.


React.createElement


Итак, мы получили множество вызовов React.createElement() и время посмотреть что же делает эта функция. Опишем на словах (а можно и в файл заглянуть – ReactElement.js).


В первую очередь она проверяет есть ли у нас пропсы (в коде объект с пропсами, который мы передали, называется config).


Далее проверяем, есть ли у нас key и ref пропсы, которые не undefined, и сохраняем их, если есть.


if (hasValidKey(config)) {
      key = '' + config.key;
}

Интересный момент, что config.key приводится к строке, а значит в качестве ключа вы можете передавать любой тип данных, главное, чтобы он реализовывал метод .toString() или .valueOf() и возвращал уникальное для конкретного набора значение.


Далее идут следующие шаги:


  • копируем пропсы, которые передали элементу;
  • добавляем туда поле children, если мы их передавали не пропсом, а как вложенным элементом;
  • ставим дефолтные значения из defaultProps для тех свойств, которые мы не определили ранее.

Когда мы подготовили все данные, мы вызываем внутреннюю функцию, которая создаёт объект, описывающий наш компонент. Выглядит этот объект следующим образом:


{
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE, // Symbol

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
}

Здесь у нас есть свойство $$typeof, которое является символом, поэтому подсунуть абы какой объект у нас не выйдет.


В свойстве type хранится тип создаваемого элемента. В случае нашего примера это будет функция App() и строки 'div', 'span' и 'input'.


Свойство key будет содержать тот самый ключ, из-за которого прилетают варнинги в консоль.
Пропсы будут содержать то, что мы передали, children и то, что было указано в defaultProps. Свойство _owner необходимо для корректной работы с ref.


Если переводить на наш пример, то результат React.createElement(App, null) выглядить это будет так:


{
    $$typeof: REACT_ELEMENT_TYPE,

    type: App,
    key: null,
    ref: null,
    props: {},

    _owner: null,
}

Кроме того, в дев режиме у нас появится дополнительное поле, которое будет использоваться для отображения красивого стека с именем файла и строкой:


_source: {
    fileName: "/Users/appleseed/react-example/src/index.js",
    lineNumber: 7
}

stack error react


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


React.useState


В версии реакта 16.8 появились хуки. Что это такое и как этим пользоваться можно прочитать по ссылке, а мы сейчас взглянем на то, что лежит в пакете react.


На самом деле, говорить много тут не придётся. По сути, пакет является фасадом, через который наши вызовы идут к внутренним сущностям.


Так, useState — это ни что иное, как две строчки кода:


export function useState<S>(initialState: (() => S) | S) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

Остальные хуки выглядят практически идентично. Здесь мы получаем текущий диспатчер, который является объектом и содержит в себе поля, например useState. Этот диспатчер меняется в зависимости от того первый у нас сейчас рендер или мы просто хотим обновить компонент.


Реальная реализация хуков хранится в пакете react-reconciler, о котором мы будем говорить в одной из следующих статей.


Что дальше


Ещё одна вещь. Прочитав эту статью, можно понять зачем мы всегда импортируем пакет реакт, даже если напрямую его не используем. Это нужно для того, чтобы после переваривания бабелем нашего jsx, у нас была переменная React.


Ребята из команды реакт озаботились этим (и не только этим) и сейчас работают над заменой createElement.


Пытаясь объяснить в двух словах: есть желание заменить текущий метод создания элементов на два – jsx и jsxs. Это нужно по нескольким причинам:


  • мы обсуждали выше, как работает createElement. Он постоянно копирует пропсы и добавляет в объект поле children, в которое сохраняет детей, которых мы передали в качестве аргументов функции (3 аргумент и далее). Сейчас предлагается делать это на этапе конвертации jsx в javascript вызовы, потому что создание элемента – это часто вызываемая функция и выполнять каждый раз модификацию пропсов в рантайме не бесплатно;
  • можно избавиться от импортирования объекта React и импортить только конкретные функции (import { jsx } from 'react', например) и, соответственно, иметь возможность не добавлять в сборку то, что нами не используется. Помимо этого, не придётся каждый раз резолвить поле createElement у объекта React, потому что это тоже не бесплатно;
  • мы обсуждали выше, что у нас есть специальный кейс, когда мы вытаскиваем из пропсов key и пробрасываем его далее. Сейчас предлагается на этапе транспайлинга брать key из jsx и передавать его третим параметром в функцию создания элемента.

Почитать подробнее можно здесь. В пакете react уже сейчас есть методы jsx и jsxs. Если хочется с этим поиграться, то можно склонировать репозиторий реакта, в файле ReactFeatureFlags.js пакета shared установить флагу enableJSXTransformAPI значение true и собрать свою версию реакта (yarn build) с включенным новым API.


Финал


На этом я закончу сегодняшний рассказ про пакет react и в следующий раз поговорим про то, как пакет react-dom использует то, что создаёт react, и какие методы и как он реализует.


Спасибо, что дочитали до конца!

  • +12
  • 5,3k
  • 3
Поддержать автора
Поделиться публикацией

Похожие публикации

Комментарии 3

    0
    Писать статьи для тех кто уже в курсе как работать с React, это очень похвально. Надеюсь это не последняя статья. Продолжайте в том же духе. Из хотелок хорошо бы рассмотреть не только FС, но и компоненты основанные на классах. Спасибо.
      0
      Спасибо за комментарий! В следующих статьях добавлю также примеры и с классами.
      0
      То что jsx превращается в «React.createElement()» большество тех кто работает с реактом знает, что оно собственно делает было интересно узнать, однако на этом все?! «Спасибо, что дочитали до конца!». А мы ведь только начали…
      Спасибо за статью, на будущее хотелось чуть больше информации.

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

      Самое читаемое