ReactJS: компоненты, элементы и экземпляры


Перевод статьи Dan Abramov "React Components, Elements, and Instances"

ReactJS: компоненты, элементы и экземпляры

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

Если вы новичок в React, то вероятно вы до этого работали только с классами компонента и экземплярами. Например, вы можете объявить компонент Button, создав класс. Когда программа запущена, вы можете иметь несколько экземпляров этого компонента на экране, каждый со своими свойствами и локальным состоянием. Это традиционное объектно-ориентированное программирование пользовательского интерфейса (UI). Зачем вводить элементы?

В традиционной модели UI, это заставляет вас заботится о создании и уничтожении экземпляров компонентов ребенка. Если компонент Form хочет отрисовать компонент Button, он должен создать его экземпляр и собственноручно держать его в курсе любой новой информации.

class Form extends TraditionalObjectOrientedView {
render() {
// Считываем некоторые данные отправленные во View
const { isSubmitted, buttonText } = this.attrs;
if (!isSubmitted && !this.button) {
// Форма еще не отправлена. Создадим кнопку!
this.button = new Button({
children: buttonText,
color: 'blue'
});
this.el.appendChild(this.button.el);
}
if (this.button) {
// Кнопка отображается. Обновим текст в ней!
this.button.attrs.children = buttonText;
this.button.render();
}
if (isSubmitted && this.button) {
// Форма отправлена. Уничтожим кнопку!
this.el.removeChild(this.button.el);
this.button.destroy();
}
if (isSubmitted && !this.message) {
// Форма отправлена. Покажем сообщение об успешной отправке!
this.message = new Message({ text: 'Success!' });
this.el.appendChild(this.message.el);
}
}
}

Это абстрактный код, но он в той или иной степени показывает, что вы получите при попытке написать составного UI, который ведет себя последовательно, согласно объектно-ориентированному принципу фреймворка наподобие Backbone.

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

Теперь давайте поговорим о React.

В React, вот где элемент приходит на помощь. Элемент является простым объектом, описывающим экземпляр компонента или узел DOM и его необходимы свойства. Он содержит только информацию о типе компонента (например, Button), его свойства (например, color) и на любый дочерние элементы внутри него.

Элемент не является фактическим экземпляром. Скорее, это способ сказать React, что вы хотите видеть на экране. Вы не можете вызвать любой метод на элемент. Это просто неизменный описанный объект с двумя полями: type: (string | Component) and props: Object.*

В случае когда тип элемента строка (string), элемент представляет собой узел DOM с типом тега, указанным в содержимом типа, и параметрами соответствующими его атрибутам. Это то, что React отрисует. Например:

{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
children: 'OK!'
}
}
}

Подобный элемент является лишь способом представить этот HTML как простой объект:

<button class='button button-blue'>
<b>
OK!
</b>
</button>

Рассмотрим как элемент может быть вложенным. Условно, когда мы хотим создать элемент-дерево, мы указываем один или более дочерних элементов как свойства ребенка содержащих их.

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

Элементы в React легко перемещать, без необходимости обработки (parsed), и, конечно, они гораздо легче, чем реальные элементы DOM?—?они просто объекты!

Однако, тип элемента может быть еще и функцией или классом соответствующим компоненту React:

{
type: Button,
props: {
color: 'blue',
children: 'OK!'
}
}

Это одна из основных идея React.

Элемент описывает компонент также является элементом, также как элемент описывающий узел DOM. Они могут быть вложенными или смешанными с другими.

Эта возможность позволяет вам определить компонент DangerButton как Button со своим значением параметра color не переживая о том, что будет ли Button отрисован как button, div или совершенно другое.

const DangerButton = ({ children }) => ({
type: Button,
props: {
color: 'red',
children: children
}
});

Вы можете смешать и подогнать их позже:

const DeleteAccount = () => ({
type: 'div',
props: {
children: [{
type: 'p',
props: {
children: 'Are you sure?'
}
}, {
type: DangerButton,
props: {
children: 'Yep'
}
}, {
type: Button,
props: {
color: 'blue',
children: 'Cancel'
}
}]
});

Или если вы предпочитаете JSX:

const DeleteAccount = () => (
<div>
<p>Are you sure?</p>
<DangerButton>Yep</DangerButton>
<Button color='blue'>Cancel</Button>
</div>
);

Это сохраняет детали компонентов разделенными друг от друга и они могут выразить отношение “имеет” и “является” только через состав.

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

Когда он видит:

{
type: Button,
props: {
color: 'blue',
children: 'OK!'
}
}

React опросит Button что он отрисовывает и получит это:

{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
children: 'OK!'
}
}
}

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

React как ребенок спрашивающий “что такое Y” для каждого “X есть Y” до тех пор, пока он не определить каждую мелочь в мире.

Помните пример Form выше? Он может быть написан в React следующим способом*:

const Form = ({ isSubmitted, buttonText }) => {
if (isSubmitted) {
// Form submitted! Return a message element.
return {
type: Message,
props: {
text: 'Success!'
}
};
}
// Form still visible! Return a button element.
return {
type: Button,
props: {
children: buttonText,
color: 'blue'
}
};
};

Вот так просто! Для компонента React параметры являются входными и дерево элементов выходными.

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

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

К коде выше Form, Message и Button являются компонентами React. Они могут быть записаны как функции, как выше, или как классы нисходящие из React.Component:

class Button extends React.Component {
render() {
const { children, color } = this.props;
// Возвращает элемент описывающий
// <button><b>{children}</b></button>
return {
type: 'button',
props: {
className: 'button button-' + color,
children: {
type: 'b',
props: {
children: children
}
}
}
};
}
}

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

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

Когда вы вызываете

ReactDOM.render({
type: Form,
props: {
isSubmitted: false,
buttonText: 'OK!'
}
}, document.getElementById('root'));

React спросит компонент Form какое дерево элемент он возвращает, учитывая полученные параметры. Это позволит постепенно “совершенствовать” его понимание вашего дерево-компонента с точки зрения простых примитивов:

// React: Вы сообщили мне это...
{
type: Form,
props: {
isSubmitted: false,
buttonText: 'OK!'
}
}
// React: ...И Form сообщила мне это...
{
type: Button,
props: {
children: 'OK!',
color: 'blue'
}
}
// React: ...и Button сообщила мне это! Я полагаю, что справился.
{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
props: {
children: 'OK!'
}
}
}
}

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

Этот постепенный процесс уточнения является также причиной простоты оптимизации приложений на React. Если некоторые части вашего дерева-компонента становятся слишком большими для React для эффективной обработки, вы можете сказать ему пропустить “уточнение” и сравнить некоторые части дерева если соответствующие параметры (props) не изменились. Очень быстро вычислить, являются ли параметры измененными если они были изменены, так React и процесс изменения великолепны вместе и могут обеспечивать великолепную оптимизацию с минимальными усилиями.

Вы могли заметить, что я много сказал о компонентах и элементах и не так иного об экземплярах. Это правда, экземпляры имеют в React гораздо меньшее значение в React, чем в большинстве объектно-ориентированных UI фреймворках.

Только компоненты были объявлены как классы имеют экземпляр и вам никогда не создать их непосредственно: React делает это за вас. /Есть несколько механизмов для экземпляра родительского компонента для доступа в экземпляр дочернего компонента, они используются только для принудительных действий (таких, как установка фокуса на поле) и их, как правило, следует избегать.

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

Вывод

Элемент?—?это простой объект описывающий, что вы хотите чтобы появилось на экране в терминах узлов DOM или других компонентов. Элементы могут содержать другие элементы в своих параметрах.

Компонент?—?может быть двух видов. Он может быть классом с методом render(), который наследуется из React.Component. Или он может быть функцией. В обоих случаях, он принимает параметры на вход и возвращает элемент дерева на выходе.

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

Вот почему люди говорят, что параметры текут в одну сторону в React: от родителя в ребенку.

Экземпляр?—?это то, на что вы ссылаетесь как this в классе компонента, который вы пишете.

Функциональный компонент не имеет экземпляров. Класс компонента имеет экземпляры, но вам нет необходимости создавать экземпляры компонента непосредственно?—?React берет на себя данную заботу.

Наконец, для создания элементов используйте React.createElement(), JSX или помощника создания элементов (element factory helper). Я отговариваю вас от написания их как простых объектов в реальном коде-просто знайте, что они являются простыми объектами “под капотом”.

Дополнительная литература

Примечание

*всем элементам React необходимо дополнительно поле $$typeof: Symbol.for(‘react.element’) на объекте по причинам безопасности. Я опускаю его вы приведенных примерах. Как правило вам необходимо использовать JSX или React.createElement() и не беспокоится об этом. Я просто хотел дать представление о сыром API, поэтому я написал встроенные объекты, но чтобы запустить этот код, вам необходимо добавить $$typeof в каждый из них.

22.12.2016 Эту страницу просмотрели за все время 5813 раз(а)


Twitter


Облако тегов