Несколько месяцев назад я думал, что запостил довольно простой вопрос в Twitter.
Я тщательно подхожу к изучению языка, но у меня до сих пор нет хорошего определения для компонента (React компонента). Есть идеи?
Меня удивила не общая путаница в этом вопросе, а количество неточных ответов, которые я получил:
Instances / Instantiation
Rendering
Evaluation
Invocation
“Using it :)”
Такая путаница происходит из-за того, что мы часто не говорим об абстрактном слое между JSX и тем, что на самом деле происходит в React. Для того чтобы ответить на этот вопрос, нам нужно погрузиться в эту абстракцию.
Давайте начнём с рассмотрения основ React. Что такое React? Это библиотека для разработки пользовательских интерфейсов. Как бы сложно React и его экосистема не выглядела, это React и его суть - разработка UI. С этой мыслью, мы подходим к нашему первому определению, Элемент (Element). Простыми словами, React элемент описывает то что вы хотите увидеть экране. А если не простыми, то React элемент описывает узел DOM в виде объекта. Обратите внимание на то, что я использовал слово описывает. Важно, что React элемент это не то что вы увидите на экране, а он описывает то что вы увидите. Для этого есть несколько причин. Первая причина заключается в том, что JavaScript объекты достаточно лёгкие и React может создавать и уничтожать их без слишком большого оверхэда. Вторая причина заключается в том, что React может анализировать объект и анализировать настоящий DOM, а затем обновлять DOM только в том месте, где произошли изменения. Это даёт некоторые плюсы в плане производительности.
Для того чтобы создать объект описывает DOM узел (он же React элемент), мы можем воспользоваться методом createElement
.
const element = React.createElement(
'div',
{id: 'login-btn'},
'Login'
)
createElement
принимает три аргумента. Первый это название тега (div, span и т.д.), второй это атрибуты которые вы хотите задать элементу и третий это содержимое или дочерний элемент, в нашем случае это текст "Login". createElement
вызываемый выше вернёт следующий объект:
{
type: 'div',
props: {
children: 'Login',
id: 'login-btn'
}
}
и когда он будет отображён в DOM (с помощью функции ReactDOM.render), у нас появиться новый DOM узел, который будет выглядеть следующим образом:
<div id='login-btn'>Login</div>
Пока что всё хорошо. Что действительно интересно в изучении React, это то что как правило учат в первую очередь - компоненты. Компоненты - это строительные блоки в React. Однако, заметьте что мы начали эту статью с элементов. Причина по которой мы это сделали, заключается в том, что если вы поймёте элементы, то плавно перейдёте к пониманию компонентов. React компонент - это функция или класс, который принимает входные данные (опционально) и возвращает React элемент.
function Button ({ onLogin }) {
return React.createElement(
'div',
{id: 'login-btn', onClick: onLogin},
'Login'
)
}
По определению, у нас есть компонент Button, который принимает функцию onLogin
и возвращает React элемент. Надо отметить, что компонент Button получает функцию onLogin
в качестве своего свойства. Чтобы передать её в наш объект описывающий DOM, мы передаём её в качестве второго аргумента метода createElement
, также как мы передавали наш атрибут id.
Давайте углубимся.
До этого момента, мы создавали элементы только с помощью нативных HTML элементов (div, span и т.д.), но мы также можем передавать другие React компоненты в качестве первого аргумента функции createElement
.
const element = React.createElement(
User,
{name: 'Tyler McGinnis'},
null
)
Однако, в отличии от имени HTML тега, если React увидит класс или функцию в качестве первого аргумента, он проверит какой элемент будет отображаться, учитывая соответствующие свойства. React будет рекурсивно продолжать делать это, пока не останется вызовов createElement
без функций или классов в качестве первого аргумента. Давайте посмотрим на это в действии.
function Button ({ addFriend }) {
return React.createElement(
"button",
{ onClick: addFriend },
"Add Friend"
)
}
function User({ name, addFriend }) {
return React.createElement(
"div",
null,
React.createElement(
"p",
null,
name
),
React.createElement(Button, { addFriend })
)
}
В примере выше, у нас есть два компонента, Button и User. Объект User будет отображён как элемент "div" с двумя дочерними элементами, "p" который оборачивает имя пользователя и компонент Button. Теперь давайте заменим вызовы функции createElement
на то что они возвращают:
function Button ({ addFriend }) {
return {
type: 'button',
props: {
onClick: addFriend,
children: 'Add Friend'
}
}
}
function User ({ name, addFriend }) {
return {
type: 'div',
props: {
children: [
{
type: 'p',
props: {
children: name
}
},
{
type: Button,
props: {
addFriend
}
}
]
}
}
}
Вы заметили, что вы приведённом выше примере у нас есть 4 разных свойства "type" - "button", "div", "p", and Button. Когда React увидит элемент с функцией или классом в типе (такой как "type: Button" выше), он обратиться к этому компоненту, чтобы узнать что он возвращает, учитывая соответствующие свойства. В конце этого процесса у React есть полный объект представления дерева DOM. В нашем примере оно выглядит следующим образом:
{
type: 'div',
props: {
children: [
{
type: 'p',
props: {
children: 'Tyler McGinnis'
}
},
{
type: 'button',
props: {
onClick: addFriend,
children: 'Add Friend'
}
}
]
}
}
Весь этот процесс в React называется "сверка" (reconciliation) и срабатывает каждый раз, когда вызываются функции setState
или ReactDOM.render
.
Теперь давайте опять взглянем на наш первоначальный вопрос:
На данный момент у нас есть все необходимые знания для ответа на этот вопрос, за исключением одной важной части. Скорей всего, если вы используете React уже на протяжении какого-то времени, то вы не используете функцию React.createElement
для создания объектов описывающих DOM. Вместо этого, вероятно вы используете JSX. Ранее я писал, что "Такая путаница происходит из-за того, что мы часто не говорим об абстрактном слое между JSX и тем, что на самом деле происходит в React". Этот абстрактный слой заключается в том, что JSX всегда компилируется в вызов React.createElement
с помощью Babel.
Взгляните на код, из нашего предыдущего примера:
function Button ({ addFriend }) {
return React.createElement(
"button",
{ onClick: addFriend },
"Add Friend"
)
}
function User({ name, addFriend }) {
return React.createElement(
"div",
null,
React.createElement(
"p",
null,
name
),
React.createElement(Button, { addFriend })
)
}
Это результат компиляции следующего JSX кода:
function Button ({ addFriend }) {
return (
<button onClick={addFriend}>Add Friend</button>
)
}
function User ({ name, addFriend }) {
return (
<div>
<p>{name}</p>
<Button addFriend={addFriend}/>
</div>
)
}
И наконец, как называется действие, когда напишем наш компонент как этот, <Icon />
?
Мы можем называть это "созданием элемента", потому что после того как JSX будет скомпилирован, именно это и произойдёт.
React.createElement(Icon, null)
Все примеры, "создания React элемента":
React.createElement('div', className: 'container', 'Hello!')
<div className='container'>Hello!</div>
<Hello />
Для получения более полной информации прочитайте статью "React Components, Instances, and Elements" от Dan Abramov.
- Войдите или зарегистрируйтесь , чтобы оставлять комментарии