Эта статья была впервые опубликована в 2015 году, на данный момент она дополнена под версию React 16.3 и ее особенности.
Компоненты (Components) — «строительные блоки» в React. Если исходить из контекста Angular, у компонентов много общего с Directives. Если вы используете другой контекст, то это, по существу, виджеты или модули.
Вы можете думать о компонентах как о коллекции HTML, CSS, JS и некоторых «внутренних данных», специфичных для этого компонента. Мне нравится сравнение компонентов с Kolaches в веб. В них есть все, что нужно, запакованное в восхитительный компонуемый сверток.
Компоненты определяются на «чистом» (pure) JavaScript или они могут быть определены на том, что команда React называет “JSX”. Если вы решите использовать JSX (который, вы, вероятнее всего, будете использовать, и это стандартно — мы также будем его использовать для нашего туториала), вам понадобится какой-то этап компиляции, чтобы преобразовать JSX в JavaScript. Но об этом мы поговорим позже.
Что делает React настолько удобным для создания пользовательских интерфейсов (UI), так это то, что данные могут быть получены либо из родительского компонента, либо содержатся в самом компоненте. Прежде чем перейти к коду, давайте удостоверимся, что у нас есть понимание уровня компонентов на уровне высшего порядка.
Выше представлено изображение моего профиля Twitter. Если мы собираемся воссоздать эту страницу на React, мы разделили бы различные секции на разные компоненты (они выделены). Обратите внимание, что компоненты могут иметь вложенные компоненты внутри себя.
Мы могли бы назвать компонент слева (розовый) UserInfo
. Внутри UserInfo
компонента у нас есть еще один компонент (оранжевый), который мы могли бы назвать UserImages
-компонентом.
Путь, по которому «работают» эти родительско-дочерние отношения — это наш UserInfo
-компонент, или родительский компонент, в котором «живет» «состояние» (“state”) данных для него самого (UserInfo), так и для дочернего компонента UserImages
.
Если мы хотим использовать какую-то часть данных родительского компонента в дочернем компоненте, то мы передадим эти данные дочернему компоненту в качестве атрибута.
В этом примере мы передаем UserImages-компоненту все изображения, которые есть у пользователя (которые в настоящее время «живут» в UserInfo
-компоненте).
Мы немного подробнее расскажем о деталях кода, но я хочу, чтобы вы поняли более общую картину того, что здесь происходит.
Эта иерархия parent / child делает управление нашими данными достаточно простым, потому что мы точно знаем, где находятся наши данные и мы не должны манипулировать ими в другом месте.
Ниже приведены темы, которые я считаю фундаментальными аспектами React. Если вы поймете их все, а также их цели, вы будете на хорошем уровне после этого туториала.
Я знаю, что это может выглядеть как «чересчур много», но вскоре вы увидите, насколько фундаментально важна каждая часть для создания надежных веб-приложений на React (и я также не шучу, когда я говорю, что хочу, чтобы это было «всесторонним» или исчерпывающим руководством).
На этом этапе вы должны понимать на «очень высоком уровне», как работает React. Теперь давайте перейдем к коду.
Продолжим и напишем наш первый компонент на React.
Чтобы создать компонент на React, будем использовать ES6-класс. Если вы не знакомы с Классами, вы можете продолжить чтение, или узнать о них больше из других источников. (например, здесь — прим.переводчика)
Обратите внимание, что единственный метод в нашем классе — это render
. Он является обязательным.
Причина — визуализация (render), описывающая пользовательский интерфейс (UI) нашего компонента. Таким образом, в этом примере текст, который будет отображаться на экране, где «рендерится» этот компонент, — Hello World!
Теперь изучим, что делает ReactDOM.
ReactDOM.render принимает два аргумента. Первый — компонент, который нужно отобразить, а второй — DOM-узел, где необходимо отобразить компонент.
Обратите внимание, мы используем ReactDOM.render
, а не React.render
. Это изменение в версии React.14, оно произведено, чтобы сделать React более «модульным». Это важно, если вы предполагаете, что React может отображать больше, чем просто DOM-элемент.
В примере выше мы просим React взять наш компонент HelloWorld и выполнить его render в элементе с ID root
.
Из-за родительских / дочерних отношений React, о которых мы говорили ранее, обычно использовать ReactDOM.render потребуется только единожды, потому что рендер «самого» родительского компонента уже предполагает отображение всех его вложенных дочерних компонентов.
Теперь на этой стадии вы можете почувствовать немного странное «бросание» «HTML» в свой JavaScript. С тех пор, как вы начали изучать веб-разработку, вам сказали, что вы должны оставить свою логику вне представления, то есть сохранить ваш JavaScript, не связанный с вашим HTML.
Эта парадигма сильна, но у нее есть свои недостатки. Я не хочу, чтобы это руководство пыталось убедить вас, что эта идея — шаг в верном направлении, поэтому, если это все еще беспокоит, вы можете проверить эту информацию. Когда вы узнаете больше о React, беспокойство должно пройти.
«HTML», который вы пишете в методе render, на самом деле не HTML, это то, что в React называется «JSX». JSX просто позволяет нам писать HTML-подобный синтаксис, который в конечном итоге преобразуется в легковесные объекты JavaScript. После чего уже React может принимать эти объекты JavaScript и от них формировать «виртуальный DOM» или представление JavaScript реального DOM («виртуальный DOM» — «легковесная копия реального DOM» — прим. переводчика). Это создает ситуацию с выигрышем, когда вы получаете доступность шаблонов с мощью JavaScript.
В примере ниже то, во что в итоге будет скомпилирован ваш JSX.
Теперь вы можете отказаться от фазы преобразования JSX -> JS и написать свои компоненты на React, например, как в коде выше. Но, как вы можете себе представить, это было бы довольно сложно. Я не знаю никого, кто не использует JSX. Для получения дополнительной информации о компиляции JSX посмотрите статьи на тему React Elements vs React Components.
До этого момента мы не выделяли особо важность этой новой виртуальной парадигмы DOM, в которую мы «запрыгиваем».
Причина, по которой команда React пошла этим путем, в том, что так как виртуальный DOM — это представление реального DOM, React может отслеживать разницу между актуальной виртуальной копией DOM, (вычисленной после изменений данных), и предыдущей, (вычисляется перед изменениями данных). Затем React изолирует изменения между старым и новым виртуальным DOM, после чего обновляет реальный DOM с необходимыми изменениями.
В более «дилетантских» выражениях, поскольку манипулирование реальным DOM происходит медленно («является более дорогостоящей операцией» — прим. переводчика), React способен минимизировать манипуляции с реальным DOM, отслеживая виртуальную копию DOM и обновляя реальный DOM только при необходимости и только с нужными изменениями.
Как правило, UI имеет множество состояний, затрудняющих управление состоянием в целом. Посредством ре-рендера виртуального DOM каждый раз, когда происходит какое-либо изменение состояния, React действительно упрощает размышления о том, в каком состоянии находится ваше приложение.
Процесс выглядит примерно так:
Некоторое пользовательское событие, которое изменяет состояние вашего приложения → ре-рендер виртуального DOM → Различие копий предыдущего виртуального DOM с новым → Обновление только реального DOM и только с необходимыми изменениями.
Поскольку это процесс преобразования от JSX к JS, необходимо настроить какую-то фазу трансформации во время разработки.
Во второй части я расскажу о Webpack и Babel для этого преобразования.
Давайте посмотрим на наш чек-лист «Самых важных частей React» и разберемся, где мы сейчас:
Мы идем в хорошем темпе. Все, что выделено «галочкой» — это то, что мы уже прошли, и вы сможете, по крайней мере, объяснить, как конкретно эти компоненты вписываются в экосистему React.
Далее по списку state
. Ранее мы говорили о том, как сложно управлять UI, потому что они обычно имеют множество разных состояний. Это случай, когда React действительно начинает «светиться».
Каждый компонент имеет возможность управлять своим собственным состоянием (state) и передавать его дочерним компонентам, если необходимо.
Возвращаясь к примеру Twitter, UserInfo
-компонент, (выделенный розовым цветом), отвечает за управление состоянием (или данными) информации пользователя.
Если другому компоненту тоже нужно это состояние / данные, но это состояние не было прямым дочерним UserInfo
, тогда вы создали бы другой компонент, который был бы прямым родителем UserInfo
и другого компонента (или обоих компонентов, которым требуется это состояние). Затем вы «прокинете» состояние вниз в качестве props в дочерние компоненты. Другими словами, у вас есть мульти-компонентная иерархия, где общий родительский компонент должен управлять состоянием и передавать его дочерним компонентам через props.
Рассмотрим примерный компонент, который использует свой state:
В этом примере мы использовали новый синтаксис. Первое, что вы заметите, — это метод конструктора. Из определения выше, метод конструктора — «способ установки состояния компонента» (начального состояния — прим.переводчика). Другими словами, любые данные, которые вы помещаете в this.state
внутри конструктора, будут частью состояния этого компонента.
В коде выше мы сообщим нашему компоненту, что хотим, чтобы он отслеживал username
. Этот username
может быть теперь использован внутри нашего компонента через {this.state.username}
, что является именно тем, что мы делаем в нашем render-методе.
Последнее о чем можно поговорить про state в том, что нашему компоненту нужна возможность изменять собственное внутреннее состояние. Мы делаем это с помощью метода, который называется setState. Помните ранее, когда мы говорили о ре-рендеринге виртуального DOM при каждом изменении данных?
Сигнал об уведомлении нашего приложения о том, что некоторые данные изменились → Ре-рендер виртуального DOM → Разница (diff) между предыдущей виртуальной копией DOM с актуальной → Обновление только реального DOM с необходимыми изменениями.
Этот «сигнал об уведомлении нашего приложения о том, что некоторые данные изменились» на самом деле просто setState. Всякий раз, когда вызывается setState, виртуальный DOM повторно ре-рендерится, выполняется алгоритм «разницы», а реальный DOM обновляется с необходимыми изменениями.
Примечание: когда мы добавляем setState в приведенный ниже код, мы также представим несколько событий, которые есть в нашем списке. Одним ударом, сразу двух зайцев.
Итак, в следующем примере кода у нас появится инпут, который, когда кто-то в него что-то пишет, автоматически обновляется наше состояние и меняет имя пользователя.
Заметьте, мы добавили еще несколько вещей. Во-первых, это handleChange
-коллбек. Этот метод будет вызываться каждый раз, когда пользователь что-то вводит в инпут.
Когда вызывается handleChange
, он будет вызывать setState
, чтобы изменить наш username на значение, которое было введено в поле ввода (e.target.value) (предпочтительнее использовать e.currentTarget.value — прим.переводчика).
Помните, всякий раз, когда вызывается setState
, React создает новый виртуальный DOM, выполняет «diff», а затем обновляет реальный DOM.
Теперь давайте посмотрим на наш render-метод. Мы добавили новую строку, содержащую поле ввода. Тип поля ввода, очевидно, будет text
. Значение будет значением нашего username (имя пользователя), которое изначально было определено в конструкторе и будет обновлено в методе handleChange
.
Обратите внимание, что есть новый атрибут, с которым вы, возможно, разньше не сталкивались — это onChange
.
Атрибут onChange
является «вещью из React», и он будет вызывать любой метод, который вы указываете, каждый раз, когда изменяется значение в поле ввода — в этом случае указанным нами методом был handleChange
. (по аналогии с onchange в нативном javascript — прим.переводчика)
Процесс для кода выше будет выглядеть примерно так:
Пользователь вводит информацию в поле ввода (текст инпут) → вызывается handleChange → состояние нашего компонента меняется на новое значение → React повторно отображается виртуальную DOM → React считывает изменения → реальный DOM обновляется.
Позже, когда мы рассмотрим props, мы увидим несколько более сложных вариантов использования состояния.
А пока, мы идем дальше! Если вы не можете объяснить то, что выделено «галочкой», прочитайте этот раздел еще раз.
Один совет по ДЕЙСТВИТЕЛЬНОМУ изучению React: не позволяйте себе пассивно читать, давая себе ложное чувство понимания материала (почему только React? Это работает везде — прим.переводчика).
Перейдите в песочницу CodeSandbox и попробуйте воссоздать примеры выше (или сделайте свои компоненты), не подглядывая в туториал. Это единственный способ начать обучение React. Это касается и этого урока, и следующего.
Мы уже несколько раз говорили о props, потому что сделать что-то толковое без их использования не выйдет. По нашему определению выше, props — это данные, которые передаются дочернему компоненту из родительского.
Это позволяет архитектуре React оставаться довольно простой. Управляйте state в высшем родительском компоненте, который нуждается в использовании специфических данных и, если у него есть потомок, которому также необходимы эти данные, «прокиньте» данные в качестве props-аргументов.
Вот очень простой пример использования props.
Обратите внимание, что в строке 9 мы имеем параметр name со значением «Tyler». Теперь в нашем компоненте мы можем использовать {this.props.name}
, чтобы получить «Tyler».
Давайте рассмотрим более продвинутый пример. Теперь у нас будет два компонента. Один — родитель, другой — потомок. Родитель должен отслеживать состояние и передавать часть этого состояния дальше до ребенка в качестве props.
Разберем родительский компонент.
Родительский компонент:
В этом компоненте не так много нового. У нас есть изначальное состояние (initial state, задано в конструкторе) и мы передаем его часть другому компоненту.
Большая часть нового кода содержится в дочернем компоненте, поэтому давайте остановимся на нем более подробно.
Компонент-потомок (дочерний):
Помните, что код, который возвращает метод render — это представление того, как должен выглядеть реальный DOM. Если вы не знакомы с Array.prototype.map, этот код может показаться вам немного странным. Все, что делает метод .map — создает новый массив, вызывает коллбек для каждого элемента массива и заполняет новый массив результатом вызова функции-коллбека для каждого элемента.
Например,
Обратите внимание, что мы сделали новый массив и добавили теги элемента списка <li></li>
к каждому элементу из оригинального массива.
Что примечательно касательно .map, этот метод отлично вписывается в React (и идет «из коробки» в JavaScript). Таким образом, в нашем дочернем компоненте выше мы просто обернули имена в тэг <li>
и сохраняем результат в переменной listItems.
Затем метод render возвращает неупорядоченный список (<ul></ul>)
со всеми нашими друзьями.
Рассмотрим еще пример, прежде чем закончим с props. Важно понимать, что везде, где есть данные, ими можно там же манипулировать. Это упрощает рассуждение о данных. Все методы getter / setter для определенной части данных всегда будут в том же компоненте, где были определены эти данные. Если вам нужно манипулировать какой-то частью данных вне того, где «живут» данные, вы передадите метод getter / setter в этот компонент в качестве props. Давайте посмотрим на такой пример.
Обратите внимание, что в нашем методе addFriend мы добавили новый способ вызова setState. Вместо того, чтобы передавать объект, мы передаем ему функцию, которая затем передается state. Всякий раз, когда вы устанавливаете новое состояние своего компонента на основе предыдущего, (как мы это делаем с нашим массивом друзей), вы хотите передать setState функцию, которая принимает текущее состояние и возвращает данные для слияния с новым состоянием.
Проверьте это в песочнице: https://codesandbox.io/embed/9o3l3pr1np.
Вы заметите, что приведенный выше код в основном такой же, как в предыдущем примере, за исключением того, что теперь у нас есть возможность добавить имя в список наших друзей. Обратите внимание, как создан новый компонент AddFriend, который управляет «новым другом», которого мы собираемся добавить.
Причина в том, что родительский компонент (FriendContainer) не знает (и не заботится) о новом знакомом, которого вы добавляете, он заботится обо всех ваших друзьях в целом (о массиве друзей).
Однако, поскольку мы придерживаемся правила манипулировать данными только из компонента, «которому не все равно», мы передали метод addFriend в наш компонент AddFriend в качестве props и мы вызываем его с new friend после вызова handleAddNew.
На этом этапе я рекомендую вам попытаться воссоздать эту же логику самостоятельно, используя приведенный выше код в качестве руководства.
Прежде чем мы пойдем дальше, хочу рассказать об еще двух возможностях React в отношении props. Это propTypes и defaultProps. Здесь я не буду вдаваться в подробности, потому что они довольно просты.
prop-types позволяют контролировать наличие или типы определенных props, переданных дочернему компоненту. С помощью propTypes вы можете указать, что требуются определенные props или что props являются определенным типом.
Начиная с React.15, PropTypes больше не включается в пакет React. Вам нужно будет установить их отдельно, запустив npm install prop-types
.
defaultProps позволяют указать значение по умолчанию (или бэкап) для определенных props только в том случае, если эти props никогда не передаются в компонент.
Я изменил наши компоненты и теперь, используя propTypes, мы ожидаем что свойство addFriend будет функцией и что оно (свойство) будет передано в компонент AddFriend. Также, используя defaultProps, мы указали, что если массив друзей не передается компоненту ShowList, он будет пустым по умолчанию.
Итак, мы на последнем участке первой части.
Давайте посмотрим, что у нас осталось по плану.
Мы близки к финалу!
У каждого компонента, который вы создаете, будут свои события жизненного цикла, которые могут быть полезны для разных манипуляций. Например, если мы хотим сделать запрос AJAX для первого рендера и получить (fetch) данные, в каком lifecycle-методе это лучше сделать? Или, если бы мы захотели запустить какую-то логику, когда props изменились, как мы это сделаем?
Различные события жизненного цикла — ответы на оба вопроса.
Рассмотрим их ниже.
componentDidMount — вызывается один раз после первого рендера. К этому моменту компонент уже есть в DOM. Если вам нужно обратиться к виртуальному DOM — к вашим услугам this.getDOMNode(). Так же это лучший метод жизненного цикла компонента для работы с запросами на сервер.
componentWillUnmount — этот метод жизненного цикла вызывается непосредственно перед тем, как компонент будет удален из DOM. Здесь вы можете произвести необходимую очистку.
getDerivedStateFromProps — иногда вам нужно обновить состояние вашего компонента на основе «пришедших» props. Это метод жизненного цикла, в котором это можно сделать. Он будет принимать props и state, а возвращаемый объект будет объединен с текущим состоянием.
Если вы дошли до этого момента, это отличная работа! Я надеюсь, что это руководство было полезно для вас и теперь вы лучше понимаете React.
Для получения более подробной информации обратитесь к курсу Tyler McGinis React Fundamentals.
Авотр оригинальной статьи — Tyler McGinnis
Перевод: freemasons + команда «Без воды»
Сегодня будем использовать parcel и IntelliJ IDEA Community Edition. Все инструменты бесплатные. Инициализация elm проекта…
На данном вебинаре мы знакомились с языком Elm проводя параллели между Elm и Redux, поэтому…
Richard Feldman рассказывает как масштабировать Elm приложение без боли. Показаны техники: extended records, подход narrow…
В данной заметке вы найдете конспект видео по Elm, которые я посмотрел в ноябре 2019.…
Итоги года 2019 // Max Frontend Покажи мне свой гитхаб, и я скажу работал ли…
Почему стоит изучать Elm? Потому что это интересный вызов, редкие (но вкусные) вакансии и хороший…
View Comments
Класс
Такой бы еще мануал и по Redux. Плюс хорошо бы по "переводным" роликам на Вашем youtube-канале делать транскрибации
Как всегда замечательно