React как определить браузер

Обновлено: 04.07.2024

Есть ли способ узнать версию React во время выполнения в браузере?

Откройте инструменты отладчика, просмотрите исходные файлы, найдите файл javascript для React и откройте его. Версии библиотек обычно печатаются вверху, даже если они минифицированы. Иногда вы также можете определить версию по имени файла. В консоли Chrome с помощью инструментов разработчика React, __REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.values().next()["value"]["version"]

React.version это то, что вы ищете.

Однако это недокументировано (насколько мне известно), поэтому это может быть нестабильная функция (то есть, хотя и маловероятно, она может исчезнуть или измениться в будущих выпусках).

Пример с React импортированным как скрипт

Пример с React импортированным модулем

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

Этот второй подход является рекомендуемым. Большинство веб-сайтов будут использовать его. create-react-app делает это ( за сценой использует webpack ). В этом случае React он инкапсулирован и, как правило, вообще недоступен вне пакета (например, в консоли браузера).

Мне удалось открыть console.log (React.version) в точке входа в мою программу, чтобы получить версию @gotofritz Нет? Я только что тестировал React 16.0.0, и он все еще работал. Какую версию реакции вы используете? Как вы его импортируете? Может быть, это зависит от того, как упаковано приложение. В приложении create-react-app нет глобального объекта React. Нет. Глобального React не существует, если вы импортируете React как модуль. В этом случае вам нужно сначала импортировать модуль и получить version свойство модуля. Это не работает на сайтах, которые используют ReactJS, и вы пытаетесь найти версию. Я пробовал это на нескольких сайтах и ​​все время получаю ошибку: Uncaught ReferenceError: require is not defined и Uncaught ReferenceError: React is not defined

Из командной строки:

Откройте Chrome Dev Tools или аналогичный и запустите require('React').version в консоли.

Это работает и на таких сайтах, как Facebook, чтобы узнать, какую версию они используют.

Пробовал . не работает с моим приложением . работающей версией react-dom v16. Это потому, что сайт, на котором вы его тестируете, не использует requirejs. @JonSchneider @WillHoskings: Интересное наблюдение. Есть ли способ обойти это, или мы застряли? @HoldOffHunger Если React не загрязняет глобальную область видимости глобальным элементом React, я не знаю.

Установив React Devtools, вы можете запустить это из консоли браузера:

Что выводит что-то вроде:

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

Не факт, что какие-либо глобальные переменные ECMAScript были экспортированы, и html / css не обязательно указывает на React. Так что загляните в .js.

Метод 1: посмотрите в ECMAScript:

Номер версии экспортируется обоими модулями react-dom и react, но эти имена часто удаляются при объединении, а версия скрывается внутри контекста выполнения, к которому нет доступа. Умная точка останова может напрямую показать значение, или вы можете выполнить поиск в ECMAScript:

  1. Загрузите веб-страницу (вы можете попробовать https://www.instagram.com, они полные Coolaiders)
  2. Откройте Инструменты разработчика Chrome на вкладке «Источники» (control + shift + i или command + shift + i)
    1. Инструменты разработчика открываются на вкладке "Источники"
    1. Ниже отображаются одно или несколько совпадений. Версия - это экспорт, очень близкий к строке поиска, которая выглядит как версия: "16.0.0"
    1. ECMAScript отображается на средней панели
    1. ECMAScript переформатирован и легче читается
    1. Если код не минифицирован, ищите ReactVersion. Должно быть 2 хита с одинаковым значением.
    2. Если код минифицирован, найдите SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED или response-dom
    3. Или найдите саму строку с вероятной версией: «15. или » 16. или даже "0,15

    Метод 2: используйте точку останова DOM:

    1. Загрузите страницу, отрисованную React
    2. Щелкните правой кнопкой мыши элемент React (что угодно, например поле ввода или поле) и выберите Inspect Element
      1. В Инструментах разработчика Chrome отображается Elements панель
      1. Примечание. Можно сравнить содержимое вкладки «Элементы» (текущее состояние DOM) с ответом на тот же ресурс на вкладке «Сети». Это может выявить корневой элемент React
      1. Инструменты разработчика Chrome останавливаются в точке останова и отображают Sources панель
      1. если строка кода выглядит так : Object (u.render) (… , наведите указатель мыши на u

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

      Как работает React: подробное руководство

      В этой статье я покажу вам, с чего начинается React .

      Что это означает? Это означает, что мы разработаем мини-версию React , которая сможет выполнять следующий код:

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


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

      Основным источником вдохновения для меня послужила эта замечательная статья.

      В конце статьи я покажу вам, как запустить проект, в котором используются ES6-модули и SASS с помощью Snowpack .

      Что здесь происходит?

      На первой строке мы определяем React-элемент . На второй - получаем ссылку на DOM-элемент . На последней - рендерим React-элемент - помещаем его в container .

      Заменим код на React обычным JavaScript .

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

      JSX трансформируется в JS с помощью таких инструментов как Babel . Трансформация, обычно, включает в себя следующее: замена кода внутри тегов на вызов функции createElement , которой в качестве аргументов передаются название тега ( type ), свойства ( props ) и дочерние элементы ( children ). Процесс трансформации JSX в JS называется транспиляцией (transilation).

      Функция React.createElement создает объект на основе переданных ей аргументов. Не считая некоторой валидации, это все, что делает данная функция.

      Таким образом, элемент - это объект с 2 свойствами: type и props . На самом деле, свойств больше, но нас пока интересуют только эти.

      type - это строка, определяющая тип DOM-элемента, который мы хотим создать. Это название тега, которое передается document.createElement для создания HTML-элемента. Это также может быть функция, о чем мы поговорим позже.

      props - это объект, содержащий все ключи и значения атрибутов JSX . Он также содержит специальное свойство children .

      В данном случае children - это строка, но, как правило, значением этого свойства является массив элементов. Вот почему элементы - это деревья (tree) с точки зрения структуры.

      render - это то "место", где React изменяет DOM .

      Сначала мы создаем узел ( node ) (во избежание путаницы я буду использовать слово "элемент" для обозначения элементов React , а слово "узел" - для обозначения элементов DOM ) на основе типа ( type ) - в данном случае h1 .

      Затем мы присваиваем узлу все пропы ( props ). В данном случае у нас имеется только заголовок ( title ).

      Далее мы создаем узлы для дочерних элементов. В данном случае у нас имеется только один такой элемент - строка. Поэтому мы создаем текстовый узел.

      Использование nodeValue вместо innerText позволит нам одинаково обрабатывать все элементы. Обратите внимание, что мы устанавливаем nodeValue так, как если бы строка имела props: < nodeValue: "Hello from MyReact!" >.

      Наконец, мы добавляем textNode в h1 , а h1 в container .

      На выходе мы получили аналогичный код, но без React .

      Приступим к реализации функции createElement .

      Если мы трансформируем JSX в JS , то получим следующее:

      Как мы выяснили, элемент - это объект с type и props . Следовательно, наша функция должна создавать такие объекты.

      Мы используем операторы spread для props и rest для children (поэтому children всегда будет массивом).

      createElement("section", null, "hello") вернет:

      createElement("section", < title: "hello" >, "hello", "world") вернет:

      Массив children может содержать примитивные значения, такие как строки или числа. Поэтому для значений с типом, отличающимся от объекта, требуется специальная функция, создающая особый тип элемента: TEXT_ELEMENT .

      React не оборачивает примитивы и не создает пустые массивы при отсутствии children . Мы жертвуем производительностью ради простоты кода.

      Заменяем React на MyReact .

      Для того, чтобы иметь возможность использовать JSX , нам необходимо указать Babel передавать трансформированный JSX в нашу функцию createElement .

      Комментарий /** @jsx MyReact.createElement */ сообщает Babel о нашем желании использовать собственную версию createElement для создания элементов.

      Далее нам необходимо реализовать собственную версию функции ReactDOM.render .

      Мы начнем с добавления узлов в DOM , а их обновление и удаление рассмотрим позже.

      Создаем новый узел на основе типа элемента и добавляем его в контейнер.

      Затем мы делаем тоже самое для каждого потомка узла рекурсивно.

      Если типом элемента является TEXT_ELEMENT , вместо обычного узла создается текстовый.

      И последнее, что нам нужно сделать, это присвоить узлу пропы элемента.

      Самым простой способ запустить этот пример (и другие):

      Конкурентный режим (Concurrent Mode)

      Перед тем, как мы продолжим веселиться, придется сделать небольшой рефакторинг кода.

      В чем проблема этого рекурсивного вызова? (Представим, что вы проходите собеседование для устройства на работу в Facebook ;) )

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

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

      Мы используем requestIdleCallback для создания бесконечного цикла. requestIdleCallback похож на setTimeout , но вместо того, чтобы выполнять задачу через определенное время, браузер запускает функцию обратного вызова (в данном случае workLoop ), когда основной поток свободен от выполнения других задач (период простоя или режим ожидания браузера - отсюда idle ).

      В React больше не используется requestIdleCallback . Теперь там применяется библиотека scheduler . По всей видимости, это объясняется тем, что requestIdleCallback является экспериментальной технологией и поддерживается не всеми браузерами. В частности, Safari поддерживает requestIdleCallback только в экспериментальном режиме.

      Подстраховаться на случай отсутствия поддержки requestIdleCallback можно так:

      Подробнее о requestIdleCallback можно почитать здесь и здесь.

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

      Для организации правильного взаимодействия между единицами работы нам нужна подходящая структура данных. Одной из таких структур является fiber tree (условно можно перевести как "древесное волокно").

      У нас имеется одно волокно для каждого элемента и каждое волокно представляет собой единицу работы.

      Рассмотрим на примере.



      Предположим, что мы хотим отрендерить такое дерево элементов:

      В методе render мы создаем корневое волокно (root fiber) и устанавливаем его в качестве nextUnitOfWork . Остальная работа выполняется в функции performUnitOfWork . Там происходит 3 вещи:

      1. Добавление элемента в DOM
      2. Создание волокон для потомков элемента
      3. Выбор следующей единицы работы


      Одной из задач этой структуры данных является упрощение определения следующей единицы работы. Вот почему каждое волокно имеет ссылки на первого потомка ( child ), сиблинга ( sibling ) и предка ( parent ).

      После обработки волокна, если у него есть child , он становится следующей единицей работы.

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

      Если волокно не имеет child , следующей единицей работы становится sibling .

      Например, волокно p не имеет child , поэтому следующей единицей работы становится a .

      Наконец, если волокно не имеет ни child , ни sibling , следующей единицей работы становится sibling предка волокна ( parent ).

      Если parent не имеет sibling , мы поднимаемся к следующему parent и так до тех пор, пока не достигнем корневого волокна. Если мы достигли такого волокна, значит, работа для данного цикла render закончена.

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

      В функции render мы присваиваем nextUnitOfWork корневой узел fiber tree .

      Когда браузер будет готов, он запустит "колбек" workLoop и начнется обработка корневого узла.

      Сначала мы создаем новый узел и добавляем его в DOM .

      Узлы содержатся в свойстве fiber.node .

      Затем для каждого потомка создается волокно.

      Новое волокно добавляется в fiber tree либо как child , если оно является первым потомком, либо как sibling .

      Наконец, мы определяем и возвращаем следующую единицу работы. Сначала мы возвращаем потомка. Если потомок отсутствует, возвращается сиблинг. Если сиблинга нет, поднимаемся к предку и возвращаем его сиблинга и т.д.

      Вот как выглядит наша функция performUnitOfWork .

      Этапы рендеринга и фиксации результатов (Commit)

      В чем проблема этого блока кода? (Второй вопрос из 100 ;) )

      Проблема в том, что мы добавляем новый узел в DOM при обработке каждого элемента (волокна). Как мы помним, браузер может прерывать процесс рендеринга для выполнения своих задач. Это может случиться до того, как мы отрендерили все дерево. Результат - пользователь видит частичный UI . Это не есть хорошо.

      Поэтому часть, мутирующую DOM , из функции performUnitOfWork мы удаляем.

      Вместо этого, мы следим за корнем fiber tree .

      После выполнения всей работы (это определяется по отсутствию следующей единицы работы) мы фиксируем (commit) fiber tree , т.е. добавляем его в DOM (рендерим).

      О том, почему мы используем здесь requestAnimationFrame описывается в материалах, посвященным requestIdleCallback , по приведенным выше ссылкам. Отличное объяснение разницы между rAF и rIC на Stack Overflow .

      Мы делаем это в функции commitRoot . Здесь мы рекурсивно добавляем все узлы в DOM .

      До сих пор мы только добавляли узлы в DOM . Но что насчет их обновления или удаления?

      Этим мы сейчас и займемся. Нам необходимо сравнивать элементы, полученные функцией render с последним fiber tree , которое мы зафиксировали в DOM .

      Нам нужно сохранять ссылку на последнее fiber tree после фиксации результатов. Назовем ее currentRoot .

      Мы также добавляем каждому волокну свойство alternate . Данное свойство - это ссылка на старое волокно, волокно, зафиксированное в DOM на предыдущей стадии рендеринга.

      Извлекаем код для создания новых волокон из performUnitOfWork в новую функцию reconcileChildren .

      Здесь мы будем сравнивать старые волокна с новыми элементами.

      Мы одновременно перебираем потомков старого волокна ( workingFiber.alternate ) и массив новых элементов для сравнения.

      Если мы опустим код для одновременной итерации по массиву и связному списку, то у нас останется 2 вещи: oldFiber и element . element - это то, что мы хотим отрендерить в DOM , а oldFiber - это то, что рендерилось в последний раз.

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

      Для их сравнения мы используем тип:

      • если старое волокно и новый элемент имеют одинаковый тип, мы сохраняет узел и только обновляем его новыми пропами
      • если типы разные и имеется новый элемент, мы создаем новый узел
      • если типы разные и имеется старое волонко, мы удаляем узел

      Здесь React также использует ключи (keys) в целях лучшего согласования. Например, с помощью ключей определяется изменение порядка элементов в списке.

      Когда старое волокно и новый элемент имеют одинаковый тип, мы создаем новое волокно, сохраняя узел из старого волокна и добавляя пропы из нового элемента.

      Мы также добавляем в волокно новое свойство action (в React используется название effectTag ). Это свойство будет использоваться на стадии фиксации.

      Индикатором необходимости создания нового узла является action: "ADD" .

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

      Но когда мы фиксируем fiber tree в DOM , мы делаем это с помощью (из) workingRoot , которое не имеет старых волокон.

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

      Мы используем этот массив при фиксации результатов.

      В функции commitWork заменяем parentNode.append(fiber.node) на следующее:

      Если fiber.action имеет значение ADD , мы помещаем новый узел в родительский узел. Если fiber.action имеет значение REMOVE , мы удаляем узел. Если fiber.action имеет значение UPDATE , мы обновляем узел новыми пропами.

      Это происходит в функции updateNode .

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

      Одним из особых пропов являются обработчики событий (event listeners). Поэтому, если название пропа начинается с on , такой проп следует обрабатывать отдельно.

      Если обработчик отсутствует или изменился, его нужно удалить.

      Затем мы добавляем новые обработчики.

      Функциональные компоненты (Functional Components)

      Добавим поддержку функциональных компонентов.

      Если мы трансформируем строку const element = <App who="MyReact" what="scratch" /> в JS , то получим следующее:

      Функциональные компоненты отличаются от обычных элементов следующим:

      • волокно функционального компонента не имеет узла
      • дочерние элементы являются результатом вызова функции

      Мы проверяем, является ли тип волокна функцией, и на основе этой проверки запускам соответствующую функцию.

      В функции updateHostComponent мы делаем тоже самое, что и раньше.

      А в updateFunctionalComponent мы запускаем переданную функцию для получения дочерних элементов.

      В нашем случае fiber.type - это функция App , выполнение которой возвращает элемент section с потомками.

      Логика согласования потомков остается прежней, нам не нужно ничего в ней изменять.

      Однако, поскольку у нас появилось волокно без узлов, нам нужно поменять 2 вещи в функции commitWork .

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

      А при удалении узла мы двигаемся вниз, пока не найден потомка с узлом. Кроме того, поскольку удаление элемента делегируется commitRemove , мы не должны запускать commitWork для старых узлов.

      Последнее, что нам осталось сделать, это добавить в функциональные компоненты состояние.

      Здесь у нас имеется простой компонент счетчика. При клике по заголовку значение счетчика увеличивается на 1.

      Мы вызываем функцию Counter , внутри которой вызывается функция useState .

      Нам необходимо инициализировать некоторые глобальные переменные для хранения информации о хуках.

      Сначала мы определяем рабочее волокно ( workingFiber ).

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

      При вызове useState мы проверяем, имеется ли у нас старый хук. Для этого мы заглядываем в свойство alternate волокна, используя индекс хука.

      Если старый хук есть, мы копируем его состояние в новый хук, иначе инициализируем состояние начальным значением (в данном случае примитивом).

      Затем мы добавляем новый хук в волокно, увеличиваем значение индекса на 1 и возвращаем состояние.

      useState также должна возвращать функцию для обновления состояния, поэтому мы определяем функцию setState , принимающую операцию (в Counter операция - это функция, которая увеличивает значение счетчика на 1).

      Мы помещаем эту операцию в очередь ( queue ) хука.

      Затем мы повторяем логику функции render : новый workingRoot становится следующей единицей работы, что приводит к запуску новой стадии рендеринга.

      Операции выполняются при следующем рендеринге компонента. Мы получаем все операции из очереди старого хука и применяем их по одной к состоянию хука. После этого мы возвращаем обновленное состояние.

      Пожалуй, на этом мы остановимся. Теперь вы знаете, с чего начать разработку собственной версии React .

      Но прежде, чем мы закончим, внесем еще несколько мелких правок.

      Запуск проекта с помощью Snowpack

      Весь код MyReact содержится в одном файле. Это не очень удобно. Но если попытаться разделить код на модули, то начнутся проблемы. Сначала мы получим ошибку, связанную с тем, что инструкция import может использоваться только в модулях. Затем исключения начнет выбрасывать Babel , потому что он не понимает синтаксис модулей - для этого ему требуется специальный плагин. Подключить плагин к Babel с помощью одного только babel.config.json не получится. Здесь нужна помощь сборщика (бандлера).

      Когда речь заходит о сборщиках, я, обычно, использую Webpack . Но недавно на Хабре вышло 2 хорошие статьи, в которых создатель snowpack делится своим опытом разработки открытого проекта. Поэтому я решил использовать этот "сборщик для сборщиков".



      Инициализируем проект, находясь в корневой директории:

      Устанавливаем snowpack , 2 плагина для него и еще один для babel :

      Настраиваем snowpack ( snowpack.config.json ):

      Настраиваем babel ( babel.config.json ):

      Определяем команду для запуска snowpack в package.json :

      Запускаем проект в режиме для разработки:

      Итак, в этой статье мы с вами реализовали мини-версию React . Она представляет собой довольно наивную имплементацию ≈0.1% исходного кода React . Но, как говорится, главное - начать ;)

      Вопрос: как проверить версию браузера до подключения библиотек к проекту?

      Заранее спасибо за любые советы!

      __________________
      Помощь в написании контрольных, курсовых и дипломных работ здесь

      Как правильно подобрать версию библиотек для MSVC2017
      Установил я тут недавно QT5.13.1 и MSVC2017. У меня Windows 7 64 и процессор Intel. При запуске.


      Как подключить строку подключения из конфигурационного файла к проекту
      Добрый день! Мне нужно подключить строку подключения из файла app.config к приложению базы данных.


      Как узнать версию и название браузера?
      После нажатия на кнопку «Information» в 2-х текстовых полях появляется: 1 поле- информация о.

      ASP, как узнать тип ОС и версию браузера?
      Podsajite pojaluista kak uznati tip operationnoi sistemi polizovatelia, tip Internet browsera i.

      Решение

      объект Map очень нов, совместим только с ие11 и выше, опера 18 того же времени выхода.

      Если вам действительно нужен его функционал, вот полифилл

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

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

      Подключение библиотек к проекту
      Доброе времени суток. Перешел с билдера на Visual Studio (VB), создаю Windows Forms, надо работать.

      Как проверить на версию?
      Делаю апдейтер. Вылезла небольшая проблема, не знаю как решить. Допустим версия приложения: 1 Я.


      Подключение своих созданных библиотек к проекту
      Хочу создать библиотеку классов и потом ее использовать! как создать понимаю. а вот как ее потом.

      image

      В этой статье мы создадим React-ивный хук usePosition() для отслеживания геолокации браузера. Под капотом этот хук будет использовать методы getCurrentPosition() и watchPosition() нативного браузерного объекта navigator.geolocation. Финальную версию хука я опубликовал на GitHub и NPM.

      Зачем создавать хук usePosition() в принципе

      Одно из важных преимуществ хуков в React-е — это возможность изолировать логически связанные фрагменты кода в одном месте (в хуке), избежав при этом необходимости смешивания логически не связанных фрагментов кода, например, в методе компонента componentDidMount() .

      Далее мы попробуем вынести функциональность, связанную с получением координат браузера, в отдельный хук usePosition() , чтобы избежать перечисленные выше трудности.

      Как мы будем использовать хук usePosition()

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

      Простейший пример получения координат и их отображения на экране может выглядеть так:

      Реализация хука usePosition()

      Наш хук usePosition() является обычной JavaScript функцией, которая выглядит так:

      Мы будем использовать хуки useState() для внутреннего хранения координат и useEffect() для подписки и отписки от слежения за координатами. Для этого мы должны их импортировать

      Создадим переменные состояния, в которых будем хранить координаты или ошибку получения координат (например, если пользователь откажется делиться своим местонахождением).

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

      А теперь ключевой момент имлементации — получение координат.

      В хуке useEffect() мы сначала делаем проверку на то, поддерживает ли браузер функциональность определения координат. Если функциональность не поддерживается, мы выходим из хука с ошибкой. Иначе мы подписываемся на изменение геопозиции браузера используя колбеки onChange() и onError() (мы добавим их код ниже). Обратите внимание, что из хука useEffect() мы возвращаем анонимную функцию, которая будет выполнена в случае, если компонент будет удаляться из отображения. В этой анонимной функции мы производим отписку от слежки, чтобы не засорять память. Таким образом вся логика подписки и отписки от слежения находится в одном хуке usePosition() .

      Давайте добавим отсутствующие колбеки:

      Хук usePosition() готов к использованию.

      Напоследок

      Вы можете найти демонстрацию работы хука и более детальную его имплементацию с возможностью задания параметров отслеживания на GitHub.



      В этой статье я покажу вам, с чего начинается React .

      Что это означает? Это означает, что мы разработаем мини-версию React , которая сможет выполнять следующий код:

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




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

      Основным источником вдохновения для меня послужила эта замечательная статья.

      В конце статьи я покажу вам, как запустить проект, в котором используются ES6-модули и SASS с помощью Snowpack .

      Введение

      Что здесь происходит?

      На первой строке мы определяем React-элемент . На второй — получаем ссылку на DOM-элемент . На последней — рендерим React-элемент — помещаем его в container .

      Заменим код на React обычным JavaScript .

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

      JSX трансформируется в JS с помощью таких инструментов как Babel . Трансформация, обычно, включает в себя следующее: замена кода внутри тегов на вызов функции createElement , которой в качестве аргументов передаются название тега ( type ), свойства ( props ) и дочерние элементы ( children ). Процесс трансформации JSX в JS называется транспиляцией (transilation).

      Функция React.createElement создает объект на основе переданных ей аргументов. Не считая некоторой валидации, это все, что делает данная функция.

      Таким образом, элемент — это объект с 2 свойствами: type и props . На самом деле, свойств больше, но нас пока интересуют только эти.

      type — это строка, определяющая тип DOM-элемента, который мы хотим создать. Это название тега, которое передается document.createElement для создания HTML-элемента. Это также может быть функция, о чем мы поговорим позже.

      props — это объект, содержащий все ключи и значения атрибутов JSX . Он также содержит специальное свойство children .

      В данном случае children — это строка, но, как правило, значением этого свойства является массив элементов. Вот почему элементы — это деревья (tree) с точки зрения структуры.

      render — это то "место", где React изменяет DOM .

      Сначала мы создаем узел ( node ) (во избежание путаницы я буду использовать слово "элемент" для обозначения элементов React , а слово "узел" — для обозначения элементов DOM ) на основе типа ( type ) — в данном случае h1 .

      Затем мы присваиваем узлу все пропы ( props ). В данном случае у нас имеется только заголовок ( title ).

      Далее мы создаем узлы для дочерних элементов. В данном случае у нас имеется только один такой элемент — строка. Поэтому мы создаем текстовый узел.

      Использование nodeValue вместо innerText позволит нам одинаково обрабатывать все элементы. Обратите внимание, что мы устанавливаем nodeValue так, как если бы строка имела props: < nodeValue: "Hello from MyReact!" >.

      Наконец, мы добавляем textNode в h1 , а h1 в container .

      На выходе мы получили аналогичный код, но без React .

      Функция createElement

      Приступим к реализации функции createElement .

      Если мы трансформируем JSX в JS , то получим следующее:

      Как мы выяснили, элемент — это объект с type и props . Следовательно, наша функция должна создавать такие объекты.

      Мы используем операторы spread для props и rest для children (поэтому children всегда будет массивом).

      createElement("section", null, "hello") вернет:

      createElement("section", < title: "hello" >, "hello", "world") вернет:

      Массив children может содержать примитивные значения, такие как строки или числа. Поэтому для значений с типом, отличающимся от объекта, требуется специальная функция, создающая особый тип элемента: TEXT_ELEMENT .

      React не оборачивает примитивы и не создает пустые массивы при отсутствии children . Мы жертвуем производительностью ради простоты кода.

      Заменяем React на MyReact .

      Для того, чтобы иметь возможность использовать JSX , нам необходимо указать Babel передавать трансформированный JSX в нашу функцию createElement .

      Комментарий /** @jsx MyReact.createElement */ сообщает Babel о нашем желании использовать собственную версию createElement для создания элементов.

      Функция render

      Далее нам необходимо реализовать собственную версию функции ReactDOM.render .

      Мы начнем с добавления узлов в DOM , а их обновление и удаление рассмотрим позже.

      Создаем новый узел на основе типа элемента и добавляем его в контейнер.

      Затем мы делаем тоже самое для каждого потомка узла рекурсивно.

      Если типом элемента является TEXT_ELEMENT , вместо обычного узла создается текстовый.

      И последнее, что нам нужно сделать, это присвоить узлу пропы элемента.

      Самым простой способ запустить этот пример (и другие):

      Конкурентный режим (Concurrent Mode)

      Перед тем, как мы продолжим веселиться, придется сделать небольшой рефакторинг кода.

      В чем проблема этого рекурсивного вызова? (Представим, что вы проходите собеседование для устройства на работу в Facebook ;) )

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

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

      Мы используем requestIdleCallback для создания бесконечного цикла. requestIdleCallback похож на setTimeout , но вместо того, чтобы выполнять задачу через определенное время, браузер запускает функцию обратного вызова (в данном случае workLoop ), когда основной поток свободен от выполнения других задач (период простоя или режим ожидания браузера — отсюда idle ).

      В React больше не используется requestIdleCallback . Теперь там применяется библиотека scheduler . По всей видимости, это объясняется тем, что requestIdleCallback является экспериментальной технологией и поддерживается не всеми браузерами. В частности, Safari поддерживает requestIdleCallback только в экспериментальном режиме.

      Подстраховаться на случай отсутствия поддержки requestIdleCallback можно так:

      Подробнее о requestIdleCallback можно почитать здесь и здесь.

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

      Волокно (Fiber)

      Для организации правильного взаимодействия между единицами работы нам нужна подходящая структура данных. Одной из таких структур является fiber tree (условно можно перевести как "древесное волокно").

      У нас имеется одно волокно для каждого элемента и каждое волокно представляет собой единицу работы.

      Рассмотрим на примере.





      Предположим, что мы хотим отрендерить такое дерево элементов:

      В методе render мы создаем корневое волокно (root fiber) и устанавливаем его в качестве nextUnitOfWork . Остальная работа выполняется в функции performUnitOfWork . Там происходит 3 вещи:

      Одной из задач этой структуры данных является упрощение определения следующей единицы работы. Вот почему каждое волокно имеет ссылки на первого потомка ( child ), сиблинга ( sibling ) и предка ( parent ).

      После обработки волокна, если у него есть child , он становится следующей единицей работы.

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

      Если волокно не имеет child , следующей единицей работы становится sibling .

      Например, волокно p не имеет child , поэтому следующей единицей работы становится a .

      Наконец, если волокно не имеет ни child , ни sibling , следующей единицей работы становится sibling предка волокна ( parent ).

      Если parent не имеет sibling , мы поднимаемся к следующему parent и так до тех пор, пока не достигнем корневого волокна. Если мы достигли такого волокна, значит, работа для данного цикла render закончена.

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

      В функции render мы присваиваем nextUnitOfWork корневой узел fiber tree .

      Когда браузер будет готов, он запустит "колбек" workLoop и начнется обработка корневого узла.

      Сначала мы создаем новый узел и добавляем его в DOM .

      Узлы содержатся в свойстве fiber.node .

      Затем для каждого потомка создается волокно.

      Новое волокно добавляется в fiber tree либо как child , если оно является первым потомком, либо как sibling .

      Наконец, мы определяем и возвращаем следующую единицу работы. Сначала мы возвращаем потомка. Если потомок отсутствует, возвращается сиблинг. Если сиблинга нет, поднимаемся к предку и возвращаем его сиблинга и т.д.

      Вот как выглядит наша функция performUnitOfWork .

      Этапы рендеринга и фиксации результатов (Commit)

      В чем проблема этого блока кода? (Второй вопрос из 100 ;) )

      Проблема в том, что мы добавляем новый узел в DOM при обработке каждого элемента (волокна). Как мы помним, браузер может прерывать процесс рендеринга для выполнения своих задач. Это может случиться до того, как мы отрендерили все дерево. Результат — пользователь видит частичный UI . Это не есть хорошо.

      Поэтому часть, мутирующую DOM , из функции performUnitOfWork мы удаляем.

      Вместо этого, мы следим за корнем fiber tree .

      После выполнения всей работы (это определяется по отсутствию следующей единицы работы) мы фиксируем (commit) fiber tree , т.е. добавляем его в DOM (рендерим).

      О том, почему мы используем здесь requestAnimationFrame описывается в материалах, посвященным requestIdleCallback , по приведенным выше ссылкам. Отличное объяснение разницы между rAF и rIC на Stack Overflow .

      Мы делаем это в функции commitRoot . Здесь мы рекурсивно добавляем все узлы в DOM .

      Согласование (Reconcilation)

      До сих пор мы только добавляли узлы в DOM . Но что насчет их обновления или удаления?

      Этим мы сейчас и займемся. Нам необходимо сравнивать элементы, полученные функцией render с последним fiber tree , которое мы зафиксировали в DOM .

      Нам нужно сохранять ссылку на последнее fiber tree после фиксации результатов. Назовем ее currentRoot .

      Мы также добавляем каждому волокну свойство alternate . Данное свойство — это ссылка на старое волокно, волокно, зафиксированное в DOM на предыдущей стадии рендеринга.

      Извлекаем код для создания новых волокон из performUnitOfWork в новую функцию reconcileChildren .

      Здесь мы будем сравнивать старые волокна с новыми элементами.

      Мы одновременно перебираем потомков старого волокна ( workingFiber.alternate ) и массив новых элементов для сравнения.

      Если мы опустим код для одновременной итерации по массиву и связному списку, то у нас останется 2 вещи: oldFiber и element . element — это то, что мы хотим отрендерить в DOM , а oldFiber — это то, что рендерилось в последний раз.

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

      Для их сравнения мы используем тип:

      • если старое волокно и новый элемент имеют одинаковый тип, мы сохраняет узел и только обновляем его новыми пропами
      • если типы разные и имеется новый элемент, мы создаем новый узел
      • если типы разные и имеется старое волонко, мы удаляем узел

      Здесь React также использует ключи (keys) в целях лучшего согласования. Например, с помощью ключей определяется изменение порядка элементов в списке.

      Когда старое волокно и новый элемент имеют одинаковый тип, мы создаем новое волокно, сохраняя узел из старого волокна и добавляя пропы из нового элемента.

      Мы также добавляем в волокно новое свойство action (в React используется название effectTag ). Это свойство будет использоваться на стадии фиксации.

      Индикатором необходимости создания нового узла является action: "ADD" .

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

      Но когда мы фиксируем fiber tree в DOM , мы делаем это с помощью (из) workingRoot , которое не имеет старых волокон.

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

      Мы используем этот массив при фиксации результатов.

      В функции commitWork заменяем parentNode.append(fiber.node) на следующее:

      Если fiber.action имеет значение ADD , мы помещаем новый узел в родительский узел. Если fiber.action имеет значение REMOVE , мы удаляем узел. Если fiber.action имеет значение UPDATE , мы обновляем узел новыми пропами.

      Это происходит в функции updateNode .

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

      Одним из особых пропов являются обработчики событий (event listeners). Поэтому, если название пропа начинается с on , такой проп следует обрабатывать отдельно.

      Если обработчик отсутствует или изменился, его нужно удалить.

      Затем мы добавляем новые обработчики.

      Функциональные компоненты (Functional Components)

      Добавим поддержку функциональных компонентов.

      Если мы трансформируем строку const element = <App who="MyReact" what="scratch" />
      в JS , то получим следующее:

      Функциональные компоненты отличаются от обычных элементов следующим:

      • волокно функционального компонента не имеет узла
      • дочерние элементы являются результатом вызова функции

      Мы проверяем, является ли тип волокна функцией, и на основе этой проверки запускам соответствующую функцию.

      В функции updateHostComponent мы делаем тоже самое, что и раньше.

      А в updateFunctionalComponent мы запускаем переданную функцию для получения дочерних элементов.

      В нашем случае fiber.type — это функция App , выполнение которой возвращает элемент section с потомками.

      Логика согласования потомков остается прежней, нам не нужно ничего в ней изменять.

      Однако, поскольку у нас появилось волокно без узлов, нам нужно поменять 2 вещи в функции commitWork .

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

      А при удалении узла мы двигаемся вниз, пока не найден потомка с узлом. Кроме того, поскольку удаление элемента делегируется commitRemove , мы не должны запускать commitWork для старых узлов.

      Хуки (Hooks)

      Последнее, что нам осталось сделать, это добавить в функциональные компоненты состояние.

      Здесь у нас имеется простой компонент счетчика. При клике по заголовку значение счетчика увеличивается на 1.

      Мы вызываем функцию Counter , внутри которой вызывается функция useState .

      Нам необходимо инициализировать некоторые глобальные переменные для хранения информации о хуках.

      Сначала мы определяем рабочее волокно ( workingFiber ).

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

      При вызове useState мы проверяем, имеется ли у нас старый хук. Для этого мы заглядываем в свойство alternate волокна, используя индекс хука.

      Если старый хук есть, мы копируем его состояние в новый хук, иначе инициализируем состояние начальным значением (в данном случае примитивом).

      Затем мы добавляем новый хук в волокно, увеличиваем значение индекса на 1 и возвращаем состояние.

      useState также должна возвращать функцию для обновления состояния, поэтому мы определяем функцию setState , принимающую операцию (в Counter операция — это функция, которая увеличивает значение счетчика на 1).

      Мы помещаем эту операцию в очередь ( queue ) хука.

      Затем мы повторяем логику функции render : новый workingRoot становится следующей единицей работы, что приводит к запуску новой стадии рендеринга.

      Операции выполняются при следующем рендеринге компонента. Мы получаем все операции из очереди старого хука и применяем их по одной к состоянию хука. После этого мы возвращаем обновленное состояние.

      Пожалуй, на этом мы остановимся. Теперь вы знаете, с чего начать разработку собственной версии React .

      Но прежде, чем мы закончим, внесем еще несколько мелких правок.

      Запуск проекта с помощью Snowpack

      Весь код MyReact содержится в одном файле. Это не очень удобно. Но если попытаться разделить код на модули, то начнутся проблемы. Сначала мы получим ошибку, связанную с тем, что инструкция import может использоваться только в модулях. Затем исключения начнет выбрасывать Babel , потому что он не понимает синтаксис модулей — для этого ему требуется специальный плагин. Подключить плагин к Babel с помощью одного только babel.config.json не получится. Здесь нужна помощь сборщика (бандлера).

      Когда речь заходит о сборщиках, я, обычно, использую Webpack . Но недавно на Хабре вышло 2 хорошие статьи, в которых создатель snowpack делится своим опытом разработки открытого проекта. Поэтому я решил использовать этот "сборщик для сборщиков".





      Инициализируем проект, находясь в корневой директории:

      Устанавливаем snowpack , 2 плагина для него и еще один для babel :

      Настраиваем snowpack ( snowpack.config.json ):

      Настраиваем babel ( babel.config.json ):

      Определяем команду для запуска snowpack в package.json :

      Запускаем проект в режиме для разработки:

      Заключение

      Итак, в этой статье мы с вами реализовали мини-версию React . Она представляет собой довольно наивную имплементацию ≈0.1% исходного кода React . Но, как говорится, главное — начать ;)

      Читайте также: