Добро пожаловать в мою серию Полное погружение в React, в которой я рассмотрю различные темы в React и попытаюсь объяснить их вам!

Тема этой статьи: JSX

Введение

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

ВАЖНО: Каждое выделенноеслово содержит ссылку на статью с дополнительными пояснениями, если вам это интересно!

Под капотом

Что такое компоненты и JSX?

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

Пример функционального компонента:

const MyComponent = ({name}) => {
   return(
     <div className="my-component">
       <h1>My Child {name}</h1>
     </div>
   )
}

Внутри этого MyComponent вы можете увидеть синтаксис HTML с нашими тегами ‹div› и ‹h1›. На самом деле это то, что известно как JSX, JavaScript XML или расширение синтаксиса JavaScript. JSX упрощает нам написание или добавление фрагментов HTML в React. Под капотом JSX предоставляет синтаксический сахар для функции React.createElement(type, props,…children).

Теперь, что это за функция и как она работает?

В конце концов, React — это просто JavaScript, а это значит, что мы можем использовать JavaScript отдельно для выполнения тех же задач, что и с React.

Давайте разберемся, как это сделать. Чтобы лучше понять, я бы порекомендовал создать HTML-файл и попытаться следовать ему. Допустим, у вас есть следующий HTML-синтаксис и вы хотите написать извечную задачу программирования «Hello World» и отобразить ее на экране:

<body>
   <div id="root"></div>
</body>

Мы хотим показать пользователю «Hello World» на экране. Для этого нам нужен доступ к этому тегу ‹div› и поместить этот текст внутри него. Для этого нам понадобится JavaScript, поэтому давайте добавим туда тег ‹script›:

<body>
   <div id="root"></div>
   <script type="module"></script>
</body>

Так что же должно находиться внутри этого тега ‹script›, чтобы мы могли получить доступ к тегу ‹div›? Внутри JavaScript у нас есть доступ к объекту HTML DOM document, который является просто объектом, представляющим всю нашу веб-страницу. Если вам нужен доступ к любому элементу на HTML-странице, вы можете получить к нему доступ с помощью объекта документа. Вот функция документа, которую мы можем использовать для доступа к нашему элементу ‹div›:

<body>
   <div id="root"></div>
   <script type="module">
      const divElement = document.getElementById('root')
   </script>
</body>

Теперь мы можем получить доступ к элементу divElement, который является нашим тегом ‹div›, и отредактировать его, используя свойство элемента textContent следующим образом:

<body>
   <div id="root"></div>
   <script type="module">
      const divElement = document.getElementById('root')
      divElement.textContent = "Hello World"
   </script>
</body>

Если все работает так, как задумано, и я случайно не ввел вас в заблуждение, теперь вы должны увидеть «Hello World» на своей веб-странице!

Круто, но в React нам нужно создавать элементы, а не просто редактировать существующие… как это работает? Что-то очень похожее происходит, когда вы можете создать элемент из объекта документа следующим образом:

<body>
   <div id="root"></div>
   <script type="module">
      const divElement = document.getElementById('root')
      const newElement = document.createElement('p')
      
      newElement.textContent = "Hello World"
      divElement.append(newElement)
   </script>
</body>

В этой версии мы получаем divElement так же, как и раньше, но вместо редактирования текстового содержимого divElement мы создаем новый тег ‹p›, редактируем текстовое содержимое тега ‹p› и добавляем его к наш divElement. Element.append() по сути просто вставляет элемент после последнего дочернего элемента выбранного вами элемента.

Вы, наверное, думаете: «Вот это здорово и все такое, но как это применимо к React?» Что ж, теперь, когда вы понимаете, как мы можем создавать/редактировать элементы в DOM с помощью JavaScript, React — это просто абстракция этого. Здесь в игру вступает React.createElement().

Если вы будете следовать инструкциям, большая часть оставшегося кода не будет работать, если только вы не найдете способ получить доступ к React на своей HTML-странице с помощью какой-либо службы, например unpkg. На момент написания статьи добавление этих двух тегов ‹script› в ваш HTML-документ должно работать:

  <script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
  <script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

Это позволит вам использовать вещи через npm, которые в противном случае не были бы доступны через тег ‹script› внутри HTML-документа.

Функция React.createElement(type, props,…children) возвращает новый элемент React заданного типа. Аргумент type может быть именем тега HTML в виде строки, например ('div' или 'p'), типом компонента React или типом фрагмента React. Аргумент props — это объект всех свойств, которые вы хотите установить для этого элемента, таких как имя класса, идентификатор и т. д.

Примечание: имя className используется в JavaScript для редактирования атрибута class, поскольку class конфликтует с ключевым словом class во многих языках, которые используются для управления DOM.

Третий аргумент, …children, является необязательным и может передаваться как часть аргумента props. Здесь вы можете передать объект дочерних узлов, таких как дополнительные элементы, textContent и т. д.

Давайте посмотрим, как это изменится на нашем текущем примере:

<body>
   <div id="root"></div>
   <script type="module">
      const divElement = document.getElementById('root')
      
      const reactElement = React.createElement(
        'p', 
        {className: 'react-element'},
        'Hello World',
      })
      
      divElement.append(reactElement)
   </script>
</body>

Чего я не упомянул, так это того, что нам нужно изменить еще одну последнюю вещь. Когда мы делаем document.createElement(‘p’), мы создаем тег ‹p›, но когда мы делаем React.createElement, мы создаем объект. Этот объект содержит все значения, которые мы только что предоставили, и будет выглядеть примерно так:

{
    "type": "p",
    "key": null,
    "ref": null,
    "props": {
        "className": "react-element",
        "children": "Hello World"
    },
    "_owner": null,
    "_store": {}
}

Единственные важные значения в этом объекте, о которых мы сейчас заботимся, — это тип (наш тег/компонент, который мы хотим отобразить) и свойства (свойства элемента). Поэтому, когда мы вызываем Element.append() для этого объекта, и он пытается преобразовать его в строку для создания узла DOM, мы просто увидим что-то вроде [object, Object] на странице вместо нашего «Hello World». Мы не хотим такого поведения, так что мы можем сделать, чтобы это исправить? Что ж, нам нужно вызвать API ReactDOM, чтобы отобразить это на странице. Пример этого можно найти здесь:

<body>
   <div id="root"></div>
   <script type="module">     
      const reactElement = React.createElement(
        'p', 
        {className: 'react-element'},
        'Hello World',
      })
      
      const root = ReactDOM.createRoot(document.getElementById('root'))
      root.render(reactElement)
   </script>
</body>

Здесь мы делаем что-то очень похожее на то, что мы делали с методами документа. Мы захватываем корневой элемент, используя API ReactDOM, и внутри этого корневого элемента мы визуализируем созданный нами reactElement. Теперь вы снова сможете увидеть «Hello World» на странице.

Примечание. В React 18 теперь у нас есть доступ к createRoot API. Раньше мы каждый раз передавали и корень, и созданный элемент в метод рендеринга, что приводило к ненужным повторным рендерингам корневого элемента.

Отлично, теперь мы знаем, как создавать компоненты в React. Но кажется чрезвычайно утомительным вызывать эту функцию каждый раз, когда нам нужен другой элемент на странице. Ну, помните JSX? JSX — это просто синтаксический сахар для вызова React.createElement().

Например:

const MyComponent = ({name}) => {
   return(
     <div className="my-component">
       <h1>My Child {name}</h1>
     </div>
   )
}

можно скомпилировать, чтобы не использовать JSX, например:

const MyComponent = ({name}) => {
   return React.createElement(
    'div',
    {className: 'my-component'},
    React.createElement('h1', null, `My child ${name}`),
  )
}

Здесь мы используем функцию React.createElement для создания тега ‹div›. Мы передаем тегу ‹div› имя класса ‘my-component’ для его объекта реквизита. Для дочерних элементов мы передаем еще один вызов React.createElement для создания тега ‹h1›, не отправляя реквизиты и устанавливая дочерние элементы как «Мой ребенок ${name}».

Вот и все. React под капотом, но, в частности, JSX под капотом. Надеюсь, это имело смысл, и если вам интересно что-то более подробное, я бы порекомендовал вам вернуться и проверить некоторые из статей, на которые я дал ссылки. Пожалуйста, дайте мне знать, если у вас есть какие-либо вопросы, и я надеюсь, что вы будете следить за моей серией React Full Dive!

Ознакомьтесь с другими моими статьями о реакции: