Это пособие по React поможет овладеть разработкой Frontend-приложений и научит хорошо разбираться в тонкостях работы методов и концепций.
Компоненты – основные строительные блоки React. Если вы изучали Angular, то компоненты очень похожи на директивы (в других языках – виджеты или модули). Компонент может представлять собой код на JavaScript или на JSX (набор HTML, CSS, JS) + информация, работающая с этим объектом. Если вы будете использовать JSX, то потребуется компиляция проекта для преобразования JSX в JavaScript.
Создание своего компонента
Пособие по React начинается с создания первого компонента.
import React from 'react' import ReactDOM from 'react-dom' class HelloWorld extends React.Component { render() { return ( <div>Hello World!</div> ) } } ReactDOM.render(<HelloWorld />, document.getElementById('root'));
Этот код выводит на экран уже привычное "Hello world".
В нашем классе присутствует единственный метод render. Каждый из компонентов содержит этот метод, т. к. он определяет внешний вид созданного интерфейса.
ReactDOM.render принимает два аргумента: объект для отображения и ноду, в которую будет установлен объект.
В описанном коде определяется, каким образом React должен оперировать с объектом HelloWorld, и как его отобразить в блоке с id root. Через архитектуру React, которая наследует отношения между компонентами по принципу “предок – потомок”, нам необходимо будет использовать в наших приложениях только ReactDOM.render т. к. рендеринг родительского компонента отобразит все вложенные в него дочерние компоненты.
Может показаться странным смешивать логику JavaScript и разметку, но в этом и есть суть использования JSX – применение связки HTML и JavaScript кода в одном объекте программы.
В процессе работы React создает из этих объектов virtual DOM, которая отображает реальную DOM в виде объектов JavaScript. Таким образом, вы сможете получить доступ к шаблонам с функционалом JavaScript.
Следующий код демонстрирует результат трансформации нашего JSX:
class HelloWorld extends React.Component { render() { return React.createElement("div", null, "Hello World"); } }
Обычно интерфейс пользователя имеет много различных состояний, что мешает контролировать его. Повторный рендеринг virtual Object Model при изменении ее состояния облегчает отслеживание текущего статуса приложения.
Описанный выше процесс можно представить так:
- некоторые действия пользователя влияют на статус приложения;
- повторный рендеринг virtual Object Model;
- сравнивается предыдущая и новая версия virtual Object Model;
- реальная Object Model принимает данные, полученные в процессе сравнения.
Изменение статуса компонента
Следующий базовый компонент, который включен в пособие по React, – это state. Каждый объект React может влиять на свое состояние и передавать эту информацию в качестве параметров дочерним объектам или при помощи props. Рассмотрим код:
class HelloUser extends React.Component { constructor(props) { super(props) this.state = { username: 'Biblprog' } } render() { return ( <div> Hello {this.state.username} </div> ) } }
В этом примере мы добавили новый метод constructor. Как говорилось ранее, это способ установки состояния компонента. Другими словами, любые данные, размещенные в this.state внутри конструктора, будут частью состояния этого компонента. В примере мы указываем нашему компоненту, что хотим отслеживать username. Это имя пользователя теперь можно применять внутри нашего компонента вызвав {this.state.username}, что мы и делаем в методе render.
Последнее, что важно помнить про state, – компоненту необходимо иметь возможность модифицировать его собственное состояние. В коде это делается при помощи метода setState. Ранее мы уже рассматривали процесс изменения состояния:
- сообщаем приложению, что состояние изменилось;
- повторный рендеринг virtual DOM;
- предыдущая версия virtual Object Model сравнивается с новой virtual DOM;
- текущая Object Model получает информацию после сравнения.
В методе setState реализовано оповещение приложения об изменении статуса данных. Во время вызова этого метода виртуальная Object Model перестраивается, находятся отличия с текущей Object Model, и происходит обновление информации после сравнения.
В коде ниже, у нас есть поле ввода input, которое в процессе ввода автоматически обновляет состояние компонента и изменяет username.
class HelloUser extends React.Component { constructor(props) { super(props) this.state = { username: 'Biblprog' } this.handleChange = this.handleChange.bind(this) } handleChange (e) { this.setState({ username: e.target.value }) } render() { return ( <div> Hello {this.state.username} <br /> Change Name: <input type="text" value={this.state.username} onChange={this.handleChange} /> </div> ) } }
Мы ввели несколько новых понятий. Метод handleChange вызывается каждый раз при введении текста в поле ввода. Когда handleChange срабатывает, вызывается setState, чтобы переназначить username независимо от входных данных (e.target.value). Помните, что при вызове setState создается обновленная виртуальная Object Model, сравнивает ее с существующей, а потом обновляет реальную Object Model.
Алгоритм работы кода из примера выше:
- пользователь вводит текст в соответствующее поле;
- срабатывает handleChange;
- изменяется state компонента;
- виртуальная Object Model перестраивается;
- сравниваются отличия;
- обновляется реальная Object Model.
Получение состояния от объекта-родителя
Передача состояния наследнику через props открывает возможность коду на React оставаться понятным.
class HelloUser extends React.Component { render() { return ( <div> Hello, {this.props.name}</div> ) } } ReactDOM.render(<HelloUser name=" Biblprog "/>, document.getElementById('root'));
В четвертой строке есть атрибут name, который хранит значение Biblprog. Теперь наш компонент сможет использовать {this.props.name} и получит в ответ Biblprog.
Рассмотрим более сложный пример: родительский компонент будет отслеживать состояние и передавать часть состояния наследнику через props.
class FriendsContainer extends React.Component { constructor(props) { super(props) this.state = { name: ' Biblprog ', friends: ['Dwight Eisenhower', 'Charles de Gaulle', 'Winston Churchill'] } } render() { return ( <div> <h3> Name: {this.state.name} </h3> <ShowList names={this.state.friends} /> </div> ) } }
В этом куске кода ничего нового не происходит, кроме появления нового класса ShowList:
class ShowList extends React.Component { render() { return ( <div> <h3> Friends </h3> <ul> {this.props.names.map((friend) => <li>{friend}</li>)} </ul> </div> ) } }
Запомните, что код, возвращаемый методом render, является репрезентацией реальной DOM, а Array.prototype.map создает массив и заполняет его значениями, которые вернутся после вызова callback-функции.
const friends = ['Dwight Eisenhower', 'Charles de Gaulle', 'Winston Churchill']; const listItems = friends.map((friend) => { return "<li> " + friend + "</li>"; }); console.log(listItems); // ["<li> 'Dwight Eisenhower' </li>", "<li> 'Charles de Gaulle' </li>", // "<li> 'Winston Churchill' </li>"];
В примере console.log возвращает ["<li> 'Dwight Eisenhower' </li>", "<li> 'Charles de Gaulle' </li>", "<li> 'Winston Churchill' </li>"]. Обратите внимание, что был создан новый массив, и к каждому элементу массива добавлено <li></li>.
Еще один важный нюанс в работе props: несмотря на расположение данных, существует определенное место для управления ими. Пособие по React пропагандирует такой подход, который упрощает манипулирование данными и позволяет сохранить простоту архитектуры.
class FriendsContainer extends React.Component { constructor(props) { super(props) this.state = { name: ' Biblprog ', friends: [ ' Dwight Eisenhower ', ' Charles de Gaulle ', ' Winston Churchill ' ], } this.addFriend = this.addFriend.bind(this) } addFriend(friend) { this.setState((state) => ({ friends: state.friends.concat([friend]) })) } render() { return ( <div> <h3> Name: {this.state.name} </h3> <AddFriend addNew={this.addFriend} /> <ShowList names={this.state.friends} /> </div> ) } }
В методе addFriend был вызван setState, в котором мы передаем функцию, а она, в свою очередь, переходит в state. Описание может казаться слегка запутанным, но дальше все прояснится.
class AddFriend extends React.Component { constructor(props) { super(props) this.state = { newFriend: '' } this.updateNewFriend = this.updateNewFriend.bind(this) this.handleAddNew = this.handleAddNew.bind(this) } updateNewFriend(e) { this.setState({ newFriend: e.target.value }) } handleAddNew() { this.props.addNew(this.state.newFriend) this.setState({ newFriend: '' }) } render() { return ( <div> <input type="text" value={this.state.newFriend} onChange={this.updateNewFriend} /> <button onClick={this.handleAddNew}> Add Friend </button> </div> ) } }
class ShowList extends React.Component { render() { return ( <div> <h3> Friends </h3> <ul> {this.props.names.map((friend) => { return <li> {friend} </li> })} </ul> </div> ) } }
Здесь мы имеем возможность добавлять имена в наш список друзей при помощи компонента AddFriend. Мы так делаем для того, чтобы придерживаться правил манипуляции нашими данными – объект AddFriend получит метод addFriend при помощи props только после вызова handleAddNew.
Дополнительные особенности работы props
- prop-types – проверяет наличие и типы props в дочернем объекте.
- propTypes – следит за тем, чтобы конкретные props были определенного типа.
- defaultProps – позволяет определить значение по умолчанию для отдельных props.
В следующем примере требуется, чтобы addFriend был функцией, передаваемой компоненту AddFriend.
import React from 'react' import PropTypes from 'prop-types' class AddFriend extends React.Component { constructor(props) { super(props) this.state = { newFriend: '' } } updateNewFriend(e) { this.setState({ newFriend: e.target.value }) } handleAddNew() { this.props.addNew(this.state.newFriend) this.setState({ newFriend: '' }) } render() { return ( <div> <input type="text" value={this.state.newFriend} onChange={this.updateNewFriend} /> <button onClick={this.handleAddNew}> Add Friend </button> </div> ) } } AddFriend.propTypes: { addNew: PropTypes.func.isRequired }
class ShowList extends React.Component { render() { return ( <div> <h3> Friends </h3> <ul> {this.props.names.map((friend) => { return <li> {friend} </li> })} </ul> </div> ) } } ShowList.defaultProps = { names: [] }
Жизненный цикл компонента
Пособие по React завершает тема жизненного цикла – важный момент в генерировании DOM.
Все компоненты, создаваемые вами, будут иметь собственные жизненные события. Например, если мы хотим сделать ajax-запрос на первоначальный рендеринг и получить ответ, в каком месте его нужно вызвать? Как использовать определенную логику, когда реквизиты компонента изменились? Ответами на все вопросы будут – различные жизненные события.
class App extends React.Component { constructor(props) { super(props) this.state = { name: ' Biblprog ' } } componentDidMount() { // Запускается после того, как компонент примонтируется к DOM } static getDerivedStateFromProps(nextProps, prevState) { // Объект, который вернется из этой функции, будет объединен с текущим состоянием } componentWillUnmount() { // Вызывается немедленно перед тем, как компонент отмонтируется // Используется для слушателей(listeners) } render() { return ( <div> Hello, {this.state.name} </div> ) } }
- componentDidMount – срабатывает один раз после первичного рендеринга. Поскольку компонент уже вызывается в этом методе, у вас есть доступ к виртуальной Object Model, и вы можете вызывать его через this.getDOMNode(). Это событие подходит для получения некоторых данных по ajax.
- componentWillUnmount – метод жизненного цикла, вызываемый непосредственно перед удалением компонента из DOM. Его используют для очистки памяти и переменных.
- getDerivedStateFromProps – этот метод поможет вам обновить статус компонента на основании полученных props.
Другие материалы по теме:
- ТОП-10 материалов и других полезностей по Angular за май 2018
- Где JavaScript джуну получать тестовые задания для практики?
- Лучшие материалы и инструменты для Front-end разработчика
Комментарии