Основы React и JSX. А также: правильная работа со State, React’s patterns, функции жизненного цикла, отлов ошибок типизации.
- Установка
- Устанавливаем create-react-app
- Создаём ваше приложение
- Исправляем ошибки
- Подключение к html
- Пример простого кода
- Базовые правила реакта
- Импорт библиотек
- Элементы
- Обёртка
- Ошибка
- Правильно
- Другой вариант
- JSX
- Запомнить
- Ключи элементов
- Структура React-проекта
- Свойства компонентов (props)
- Можно передавать в компоненты свойства и значения.
- Передача данных как объекта
- Деструктурировать можно при объявлении компонента
- Классы-компоненты
- Передача данных в компонент
- defaultProps
- Свойство propTypes
- Библиотека prop-types
- Установка
- Импорт
- Использование
- Состояние компонента (state)
- Создание state:
- Достаём данные из state
- Устанавливаем новые значения
- Обработка событий
- Обновление состояния в зависимости от предыдущего
- Изменение массива или объекта в стейте
- Неправильно
- Правильно
- Данные
- Работа с формами
- Методы жизненного цикла (lifecycle hooks)
- Паттерны React’а
- Ребёнок
- Клонирование элементов
- Неправильно
- Правильно
- Contex.API
- Источники
Установка
Устанавливаем create-react-app
Вводим в консоль / терминал: npm i -g create-react-app
Создаём ваше приложение
create-react-app name
, где ‘name’ — название вашего приложения
cd name
— переходим в папку с приложением, npm start
— запускаем
Исправляем ошибки
Если после npm start падают ошибки типа: There might be a problem with the project dependency tree. It is likely not a bug in Create React App, but something you need to fix locally,
то одним из вариантов может быть создание файла .env со следующим содержимым:
SKIP_PREFLIGHT_CHECK=true
Это отключает проверку и позволяет работать.
Конфликтуют обычно компоненты установленные также глобально — webpack, babel и другие.
Поэтому другой вариант решения — удалить глобально установленный конфликтующий компонент.
Подключение к html
В вашем html-файле должен быть id, в который будет выводиться результат работы реакта. Например:
<div id="root"></div>
Пример простого кода
import React from 'react'; // импортируем реакт
import ReactDOM from 'react-dom'; // импортируем jsx
const el = (
<div>
<h1>My Todo List</h1>
<input placeholder="search" />
<ul>
<li>Learn React</li>
<li>Build Awesome App</li>
</ul>
</div>
);
ReactDOM.render(el,
document.getElementById('root')); // выводит ваш реакт код в браузер, в <div id="root"></div>
Базовые правила реакта
Импорт библиотек
Вы всегда должны подключить в компонент реакт:
import React from 'react'; // импортируем реакт
а если используете в компоненте jsx-рендеринг, то дополнительно библиотеку react-dom:
import ReactDOM from 'react-dom'; // импортируем jsx
Элементы
Функции, которые возвращают React элемент, должны начинаться с большой буквы
const Header = () => {
return <h1>Hi, world</h1>
}
ReactDOM.render(<Header />,
document.getElementById('root'));
Обёртка
Нужно всегда возвращать только один DOM-узел верхнего уровня.
Ошибка
const Header = () => {
return (
<h1>Hi, world</h1>
<p>Hi, user</p>
)
}
Правильно
const Header = () => {
return (
<div>
<h1>Hi, world</h1>
<p>Hi, user</p>
</div>
)
}
Другой вариант
Чтобы не создавать лишних элементов обёртки, можно использовать React.Fragment
const Header = () => {
return (
<React.Fragment>
<h1>Hi, world</h1>
<p>Hi, user</p>
</React.Fragment>
)
}
JSX
Вы наверняка заметили, что код местами похож на html, но это jsx, который немного отличается от html.. Например, атрибуты пишутся camelCase’ом.
html:
<p style="margin-left: 10px;">Hi, user</p>
jsx:
<p style="marginLeft: 10px;">Hi, user</p>
Запомнить
Некоторые атрибуты пишутся по другому:
- не «class», а «className»;
- не «for», а «htmlFor»
Ключи элементов
Для ускорения рендеринга (отрисовки DOM) нужно присваивать уникальные ключи в тех областях элементов, которые будут изменяться
const List = () => {
return (
<div>
<h1>Список участников:</h1>
<ul>
<li key='1'>Иванов И.И.</li>
<li key='2'>Петров П.П.</li>
<li key='3'>Сидоров С.С.</li>
</ul>
</div>
)
}
Ключи не должны переиспользоваться. Т.е. если вы удаляется элемент с key=’1′, такой ключ не нужно присваивать другому элементу.
Структура React-проекта
- Компоненты храним в папке components,
- один компонент — один файл,
- хороший компонент должен быть независимым
Свойства компонентов (props)
Можно передавать в компоненты свойства и значения.
const Header = props => {
const {color, fontSize, name} = props;
return (
<h1 style={{color, fontSize}}>Hi, {name}</h1>
)
}
ReactDOM.render(<Header color="red" fontSize= '48px' name="Ivan" />,
document.getElementById('root')); // выведет заголовок "Hi, Ivan" красного цвета, размером 48px
Передача данных как объекта
const data = { color: 'green', fontSize: '48px', name: 'Ivan'}
const Header = props => {
const {color, fontSize, name} = props;
return (
<h1 style={{color, fontSize}}>Hi, {name}</h1>
)
}
ReactDOM.render(<Header {...data} />,
document.getElementById('root'));
Деструктурировать можно при объявлении компонента
const data = { color: 'red', fontSize: '48px', name: 'Ivan'}
const Header = ({color, fontSize, name}) => {
return (
<h1 style={{color, fontSize}}>Hi, {name}</h1>
)
}
ReactDOM.render(<Header {...data} />,
document.getElementById('root'));
Классы-компоненты
Используются, когда нужно хранить состояние (state).
Передача данных в компонент
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
const data = { color: 'blue', fontSize: '48px', name: 'Ivan'}
class Header extends Component {
render() {
const {color, fontSize, name} = this.props;
return (
<h1 style={{color, fontSize}}>Hi, {name}</h1>
)
}
}
ReactDOM.render(<Header {...data} />,
document.getElementById('root'));
defaultProps
defaultProps — позволяет установить значение свойств по умолчанию
const Welcome = ({ name }) => (<h1>Hi, {name}</h1>);
Welcome.defaultPrors = { name: 'Masha' };
<Welcome /> // Hi, Masha
В классах
Class Welcome extends Component {
static defaultProps = { name: 'Masha' };
...
}
Свойство propTypes
Позволяет проверять типы данных в React-компонентах
Class AgeCheck extends Component {
static defaultProps = { age: 18 };
static propTypes = {
age = (prop, propName, componentName) => {
const value = prop[propName];
if (typeof value === 'number' && !isNaN(value)) {
return null;
}
return new TypeError(`${componentName}: ${propName} must be a number`);
}
Проверка срабатывает после defaultProps и возвращает null, если всё проверка прошла корректно, или возвращает объект Error
Библиотека prop-types
Установка
npm install --save prop-types
Импорт
import PropTypes from 'prop-types'; // ES6
Использование
class MyComponent extends React.Component {
render() {
// ... do things with the props
}
}
MyComponent.propTypes = {
// You can declare that a prop is a specific JS primitive. By default, these
// are all optional.
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// Anything that can be rendered: numbers, strings, elements or an array
// (or fragment) containing these types.
optionalNode: PropTypes.node,
// A React element.
optionalElement: PropTypes.element,
...
Все варианты здесь: https://github.com/facebook/prop-types
Состояние компонента (state)
State — единственных источник значений. Сначала обновляется стэйт, а потом перерисовывается страница.
Создание state:
Вариант 1, в конструкторе
constructor() {
super();
this.state = {
color: 'red',
fontSize: '36px',
name: 'Иван'
}
Вариант 2. Вероятный будущий синтаксис (на сентябрь 2018 ещё не утверждён окончательно)
state = {
color: 'red',
fontSize: '36px',
name: 'Danila'
}
После установки state его менять нельзя, но можно устанавливать новые данные в переменные стейта (обновлять).
Достаём данные из state
const {color, fontSize, name} = this.state; // достаём данные из стейта через деструктурирование
Устанавливаем новые значения
this.setState = {
color: 'blue',
name: 'Danila'
}
Обработка событий
Обработчики пишутся camelCase’ом. Пример: `onClick`
class Header extends Component {
clicked = () => {
console.log(`You clicked on title`)
}
render() {
return (
<h1 onClick= { this.clicked }>Hi, world</h1>
)
}
}
Обновление состояния в зависимости от предыдущего
Мы хотим, чтобы по клику наша надпись менялась туда / сюда. Но дело в том, что state обновляется не мгновенно, а с небольшой задержкой. Поэтому, проверяя state, нужно убедится, что он уже обновился.
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
class Header extends Component {
state = {
color: 'red',
fontSize: '36px',
name: 'Ivan'
}
changeHeader = () => {
this.setState((state) => {
let currentName = state.name;
if (currentName === 'Ivan') {
return {
color: 'blue',
name: 'Danila'
}
}
else return {
color: 'red',
name: 'Ivan'
}
});
}
render() {
const {color, fontSize, name} = this.state; // достаём данные из стейта
return (
<h1 onClick={this.changeHeader}
style={{color: color, fontSize: fontSize}} >Hi, {name}</h1>
)
}
}
ReactDOM.render(<Header />,
document.getElementById('root'));
Изменение массива или объекта в стейте
Прямое изменение массива в стейте запрещено и считается очень плохой практикой, т.к. может приводить к трудно находимым ошибкам.
Неправильно
state = { arr = [ 1, 2, 3] }
this.setState = { arr[2] = 5; }
Правильно
state = { arr = [ 1, 2, 3] }
this.setState = { return {arr: [1, 2, 5] } }
Данные
- Рекомендуется управлять данными централизовано,
- если данные требуются в нескольких компонентах, их нужно хранить в родительском;
- чтобы отправить данные вверх по иерархии используются события
Работа с формами
- onChange() — получение текущего значения,
- onSubmit() — «отправка» формы,
- e.preventDefault() — отмена перезагрузки страницы
Методы жизненного цикла (lifecycle hooks)
- componentDidMount() — компонент уже подключён, его DOM-элементы уже отображаются. Используется для инициализации (получение данных, работа с DOM).
- componentDidUpdate() — вызывается после обновления компонента (после render() ). В нём можно запрашивать новые данных для обновлённых элементов. Важно проверить действительно ли обновился state, иначе может быть бесконечный цикл обновления.
- componentWillUnmount() — компоненты будет удалён, но его DOM ещё на странице. Используется для очистки ресурсов (таймеры, интервалы, запросы к серверу).
- componentDidCatch() — срабатывает, когда в компоненте, или его детях произошла ошибка. Не обрабатывает ошибки в event listeners и в асинхронном коде. Принимает два аргумента: error и info с дополнительной информацией об источнике ошибок.
Паттерны React’а
- Функция может инкапсулировать получение данных (и сделать компонент не зависимым от источника данных).
- Render-функция — возвращает строку или React-элемент.
<Header welcome={ () => <p>Hi, Vasya</p> } />
- Свойства-элементы. В качестве значения свойств можно передать элемент:
<Header title={ <h1>Hi, Masha</h1> />
. Так можно создавать элементы-контейнеры, или элементы, которые умеют выбирать что рендерить в зависимости от условия.
Ребёнок
Компоненту можно передать одно из свойств, поместив его в тело элемента. <Header>Hi, Alex</Header>.
Это свойство доступно через props.children
. Поддерживает любые типы данных: элементы, функции, объекты.
Функция React.Children.map() упрощает обработку props.children:
{ React.Children.map( this.props.children, (child) => {
// функция обработки child'а.
} }
Клонирование элементов
Реакт-элементы нельзя изменять после того, как они были созданы.
Неправильно
element.new = new; // так нельзя
Правильно
return React.cloneElement(element, new);
Contex.API
const {Provider, Consumer} = React.CreateContext();
// Provider делает доступным value внутри компонентов, которые оборачивает
const Name = () => {
return 'Lena';
}
<Provider value = { this.Name } >
<Component1 />
...
<ComponentN />
</Provider>
Внутри компонента значения извлекаются с помощью Consumer
<Consumer>
{
({ name }) => {
return (<h1>Hi, {name}</h1>)
}
}
</Consumer>