React Course Ru v2 PDF
React Course Ru v2 PDF
Вступление 1.1
От автора 1.1.1
Подключаем react как script 1.2
Создание компонента 1.3
Использование props 1.4
If-else, тернарный оператор 1.5
Порефакторим... 1.6
Prop-types 1.7
Использование state 1.8
Подробнее о state 1.8.1
Работа с input 1.8.2
Жизненный цикл компонента 1.9
Работа с формой 1.10
Добавить новость 1.10.1
Итоги по основам 1.11
create-react-app 1.12
Приборка и импорты 1.12.1
Асинхронные запросы 1.13
Спам-фильтр 1.14
componentWillReceiveProps 1.14.1
getDerivedStateFromProps 1.14.2
Порефакторим... 1.14.3
Заключение 1.15
1
Вступление
1. HTML/CSS
2. Javascript (или хотя бы jQuery, если вы понимаете, что $ всего лишь функция...)
Поддержать проект
Вы можете поддержать проект, мне будет очень приятно.
2
От автора
Обо мне
Напутствие
Пожалуйста, выполняйте код по ходу книги. Ломайте его, "консольте", интересуйтесь.
Полезные ссылки
Мои уроки/вебинары/соц.сети:
3
От автора
Актуальный прайс
4
Подключаем react как script
index.html
В мире программирования, каждый урок начинается с hello, world.
index.html
5
Подключаем react как script
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React [RU] Tutorial</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
</script>
</body>
</html>
<h1>Привет, мир!</h1>,
6
Подключаем react как script
7
Создание компонента
Создание компонента
ReactDOM.render принимает react-компонент (далее буду называть просто
"компонент") и DOM-элемент, в который мы хотим "примонтировать" наше
приложение.
ReactDOM.render(
<App>
<Photos photos=photos />
<LastNews />
<Comments />
</App>,
document.getElementById('root')
);
Хочу вас обрадовать, React.js код выглядит практически так же. Он отлично читается,
так как деление на компоненты позволяет отлично структурировать код.
index.html
8
Создание компонента
<!DOCTYPE html>
...
<body>
<div id="root"></div>
<script type="text/babel">
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>
</body>
</html>
Что примечательного? Мы скрыли в <App /> разметку. Да, в этом примере это одна
строка и чувство эйфории отсутствует, но то ли еще будет! Пока запомним, что если
мы хотим отрисовать в JSX компонент, то мы обязательно должны называть и
вызывать его с Большой буквы.
9
Создание компонента
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React [RU] Tutorial v2</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script
>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
<style>
.red {
color: #FF0000;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
ReactDOM.render(
<BigApp />,
document.getElementById('root')
);
</script>
</body>
</html>
10
Создание компонента
Синтаксис:
так же позволяет создать компонент. Здесь стоит отметить, что если компонент создан
с помощью класса, то JSX разметка пишется внутри метода render. Это ключевой
метод, в котором мы указываем, что будет отображаться пользователю на странице.
В примере мы добавили стиль для параграфа, через className , а не через class , как
мы привыкли делать это. Почему? Потому что, мы находимся внутри JSX-синтаксиса,
в котором html и js идут вперемешку, а слово class зарезервировано в javascript.
В разметке все как мы и ожидали. Однако, я уже вижу читателей, кому не нравится
лишний div .
11
Создание компонента
index.html
<script type="text/babel">
ReactDOM.render(
<BigApp />,
document.getElementById('root')
);
</script>
12
Создание компонента
Ошибка: jsx-элементы должны быть обернуты в один тэг. Что делать, если не хочется
городить еще один div? Ответ: React.Fragment
Все довольны. Разницы особо нет, как вам больше нравится, так и пишите, но
помните: все что вы возвращаете в render методе или в return у stateless-компонента
должно быть обернуто в один тэг / React.Fragment.
13
Создание компонента
Давайте разовьем идею: научим BigApp отображать новости. Для этого, нам
потребуется создать компонент <News /> и вложить его в BigApp.
index.html
ReactDOM.render(
<BigApp />,
document.getElementById('root')
);
Во-вторых, как уже было сказано - компонент <BigApp /> содержит в себе компонент
<News /> , словно это просто дочерний <div></div> элемент.
В-третьих, наш компонент <News /> такой же примитивный, как и App, поэтому мы
создали его через функцию (а не через class).
14
Создание компонента
Решение:
index.html
15
Создание компонента
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React [RU] Tutorial v2</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script
>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
<style>
.red {
color: #FF0000;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>
</body>
</html>
Прежде чем переходить к следующему уроку, предлагаю вам установить react devtools
(плагин для хрома, плагин для мозилы).
16
Создание компонента
17
Создание компонента
18
Создание компонента
19
Использование props
Использование props
У каждого компонента могут быть свойства. Они хранятся в this.props , и передаются
компоненту как атрибуты.
Общий вид:
20
Использование props
const myNews = [
{
author: 'Саша Печкин',
text: 'В четверг, четвертого числа...'
},
{
author: 'Просто Вася',
text: 'Считаю, что $ должен стоить 35 рублей!'
},
{
author: 'Max Frontend',
text: 'Прошло 2 года с прошлых учебников, а $ так и не стоит 35'
},
{
author: 'Гость',
text: 'Бесплатно. Без смс, про реакт, заходи - https://maxpfrontend.ru'
}
];
index.html
21
Использование props
...
const myNews = [
{
author: 'Саша Печкин',
text: 'В четверг, четвертого числа...'
},
{
author: 'Просто Вася',
text: 'Считаю, что $ должен стоить 35 рублей!'
},
{
author: 'Max Frontend',
text: 'Прошло 2 года с прошлых учебников, а $ так и не стоит 35'
},
{
author: 'Гость',
text: 'Бесплатно. Без смс, про реакт, заходи - https://maxpfrontend.ru'
}
];
ReactDOM.render(
<App />,
document.getElementById('root')
);
...
Обратите внимание, комментарии внутри JSX пишутся в фигурных скобках: {/* текст
комментария */}
Так же прошу заметить, что JSX это не весь js-код, который содержится в тэге script,
грубо говоря JSX - это HTML-разметка + переменные/выражения. Поэтому в
остальных местах можно писать комментарии привычным для вас способом ( //... или
/*...*/ )
22
Использование props
Напомню: в данный момент, мы добавили свойство data в наш компонент <News /> .
Необязательно было называть свойство так, можно было написать, например:
23
Использование props
Ок, у нашего компонента есть свойство, в котором лежат наши новости, но компонент
не умеет их отображать. Это легко исправить.
<div class="news">
<p class="news__author">Саша Печкин:</p>
<p class="news__text">В четверг, четвертого числа...</p>
</div>
Вопрос: У нас есть разметка для одного элемента данных, есть данные целиком
(массив myNews). Как отобразить эти данные?
Когда вам нужно отобразить переменную в шаблоне разметки JSX - она так же
оборачивается в фигурные скобки. На практике это выглядит проще, чем в теории,
поэтому давайте представим, как может выглядеть наша JSX разметка:
24
Использование props
this.props.data.map(function(item, index) {
return (
<div key={index}>
<p className="news__author">{item.author}:</p>
<p className="news__text">{item.text}</p>
</div>
)
})
index.html
25
Использование props
...
const myNews = [
{
author: 'Саша Печкин',
text: 'В четверг, четвертого числа...'
},
{
author: 'Просто Вася',
text: 'Считаю, что $ должен стоить 35 рублей!'
},
{
author: 'Max Frontend',
text: 'Прошло 2 года с прошлых учебников, а $ так и не стоит 35'
},
{
author: 'Гость',
text: 'Бесплатно. Без смс, про реакт, заходи - https://maxpfrontend.ru'
}
];
console.log(newsTemplate)
return (
<div className="news">
{newsTemplate}
</div>
)
}
}
...
26
Использование props
27
Использование props
Взглянем в консоль:
28
Использование props
P.S. Здесь и в продолжении всего курса в коде для отображения массива новостей
используется key = {index} . Обратите внимание на следующую ветку
комментариев (спасибо DeLaVega и geakstr).
Суть в том, что index не лучший вариант для ключа, когда ваши "айтемы" могут менять
порядок. У нас новости не меняются, но тем не менее, мы можем быстро решить нашу
проблему добавив "действительно" уникальное значение, которое не будет
изменяться, если у элементов поменяется index.
Конечно же, это свойство называется id ;) Добавим его в массив и будем использовать
в качестве ключа.
index.html
29
Использование props
...
const myNews = [
{
id: 1, // добавили id
author: 'Саша Печкин',
text: 'В четверг, четвертого числа...'
},
{
id: 2,
author: 'Просто Вася',
text: 'Считаю, что $ должен стоить 35 рублей!'
},
{
id: 3,
author: 'Max Frontend',
text: 'Прошло 2 года с прошлых учебников, а $ так и не стоит 35'
},
{
id: 4,
author: 'Гость',
text: 'Бесплатно. Без смс, про реакт, заходи - https://maxpfrontend.ru'
}
];
return (
<div className="news">
{newsTemplate}
</div>
)
}
}
...
30
Использование props
31
If-else, тернарный оператор
Для начала, научимся отображать общее количество новостей, допустим внизу, после
списка новостей.
Как бы сказал участник игры "Угадай JS" - я напишу это за одну строку. Что скажете
вы? Подсказка:
return (
<div className="news">
{newsTemplate}
{/* эта строка здесь */}
</div>
)
}
}
Ответ:
32
If-else, тернарный оператор
Компонент News:
return (
<div className="news">
{newsTemplate}
<strong>Всего новостей: {data.length}</strong>
</div>
);
}
}
33
If-else, тернарный оператор
if (data.length)
// можно представить как
if (data.length > 0)
Если нет новостей - зачем нам показывать, что всего новостей 0? Давайте решим это с
помощью css класса .none, который будем добавлять если новостей нет.
.none {
display: none;
}
Проще простого: есть новости ? ' пустой класс ' : ' класс .none '
34
If-else, тернарный оператор
Мы скрыли наш элемент strong c помощью класса, но при таком подходе элемент
остался в DOM-дереве. Можем исправить это, не отображая элемент вовсе.
...
return (
<div className="news">
{newsTemplate}
{
data.length ? <strong>Всего новостей: {data.length}</strong> : null
}
</div>
);
...
Итого: если вам нужно отобразить что-то в зависимости от условий, делайте это так
же, как если бы react не был подключен, но не забывайте, что "условия" внутри JSX
пишутся в фигурных скобках. Для удобства, мы использовали переменную-шаблон,
которую объявили заранее, а затем в зависимости от условия сохраняли в нее
необходимую разметку.
35
If-else, тернарный оператор
36
Порефакторим...
Порефакторим...
Для начала, удалите вовсе компонент <Comments /> (и const Comments...
соответственно).
Задача: <News /> должен рендерить список компонентов <Article /> . Каждый
компонент <Article /> должен получать соответствующие данные, например: первый
экземпляр получит первый элемент массива, второй - второй и так далее.
if (data.length) {
newsTemplate = data.map(function(item) {
return <Article key={item.id} data={item}/>
})
} else {
newsTemplate = <p>К сожалению новостей нет</p>
}
37
Порефакторим...
38
Порефакторим...
.none {
display: none;
}
body {
background: rgba(0, 102, 255, 0.38);
font-family: sans-serif;
}
p {
margin: 0 0 5px;
}
.article {
background: #FFF;
border: 1px solid rgba(0, 89, 181, 0.82);
width: 600px;
margin: 0 0 5px;
box-shadow: 2px 2px 5px -1px rgb(0, 81, 202);
padding: 3px 5px;
}
.news__author {
text-decoration: underline;
color: #007DDC;
}
.news__count {
margin: 10px 0 0 0;
display: block;
}
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React [RU] Tutorial v2</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script
>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
<style>
.none {
display: none;
}
body {
39
Порефакторим...
p {
margin: 0 0 5px;
}
.article {
background: #FFF;
border: 1px solid rgba(0, 89, 181, 0.82);
width: 600px;
margin: 0 0 5px;
box-shadow: 2px 2px 5px -1px rgb(0, 81, 202);
padding: 3px 5px;
}
.news__author {
text-decoration: underline;
color: #007DDC;
}
.news__count {
margin: 10px 0 0 0;
display: block;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const myNews = [
{
id: 1, // добавили id
author: 'Саша Печкин',
text: 'В четверг, четвертого числа...'
},
{
id: 2,
author: 'Просто Вася',
text: 'Считаю, что $ должен стоить 35 рублей!'
},
{
id: 3,
author: 'Max Frontend',
text: 'Прошло 2 года с прошлых учебников, а $ так и не стоит 35'
},
{
id: 4,
author: 'Гость',
text: 'Бесплатно. Без смс, про реакт, заходи - https://maxpfrontend.ru'
}
];
40
Порефакторим...
if (data.length) {
newsTemplate = data.map(function(item) {
return <Article key={item.id} data={item}/>
})
} else {
newsTemplate = <p>К сожалению новостей нет</p>
}
return (
<div className="news">
{newsTemplate}
{
data.length ? <strong className={'news__count'}>Всего новостей: {data.
length}</strong> : null
}
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>
41
Порефакторим...
</body>
</html>
В целом меня устраивает почти все. Осталось немного отполировать метод render
компонента <News /> . Правило следующее: стараемся в render держать как можно
меньше кода, чтобы его было легко читать вашим коллегам. Для этого, мы
newsTemplate будем заполнять внутри нового метода, который будем вызывать в
render.
if (data.length) {
newsTemplate = data.map(function(item) {
return <Article key={item.id} data={item}/>
})
} else {
newsTemplate = <p>К сожалению новостей нет</p>
}
return newsTemplate
}
render() {
const { data } = this.props
return (
<div className="news">
{this.renderNews()}
{
data.length ? <strong className={'news__count'}>Всего новостей: {data.length}
</strong> : null
}
</div>
);
}
}
42
Порефакторим...
Далее, так как мы renderNews создали в качестве метода, значит внутри компонента,
мы должны обращаться к нему как this.XXX (где XXX - название метода, в нашем
случае renderNews ).
Что же изменилось, спросите вы? Было много кода в render, стало чуть выше. Дело в
том, что когда ваши компоненты разрастутся, очень удобно иметь "рендер" хорошо
читаемым, то есть таким, в котором все лишнее спрятано.
43
Prop-types
Prop-types
(скучный, но небольшой теоретический перекур)
44
Prop-types
<script src="https://unpkg.com/[email protected]/prop-types.js"></script>
Пусть вас не смущает версия 15.6 у пакета prop-types. Он с некоторых пор живет своей
жизнью, отдельно от react.
45
Prop-types
...
class News extends React.Component {
renderNews = () => {
...
}
render() {
...
}
}
// добавили propTypes.
// propTypes (с маленькой буквы) = свойство News
News.propTypes = {
data: PropTypes.array.isRequired // PropTypes (с большой буквы) = библиотека prop-ty
pes
}
...
46
Prop-types
Обновите страницу:
Гораздо лучше! Исходя из текста ошибки нам сразу понятно куда копать: в render
методе App, не указано свойство data, которое ожидается в News. Справедливости
ради, ниже есть подобное сообщение, но ошибка там имеет универсальный текст.
Подробные "простыни" текста об ошибке - заслуга новых версий реакта. Это удобно.
47
Prop-types
Подробнее о propTypes
Приведу выдержку из офф.документации:
48
Prop-types
MyComponent.propTypes = {
propTypes: {
// Вы можете указать, каким примитивом должно быть свойство
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// ...
// ...
// Если нужно указать, что свойство просто обязательно, и может быть любым примити
вом
requiredAny: React.PropTypes.any.isRequired,
должно быть!
Я вижу по глазам некоторых (да-да, вижу), что все это какая-то бесполезная лабуда.
Итак понятно - есть ошибка, есть возможность тыкнуть на нее в дебаггере и
посмотреть. Специально для вас, следующая ситуация: удалите из массива myNews
49
Prop-types
const myNews = [
{
id: 1,
author: 'Саша Печкин',
text: 'В четверг, четвертого числа...',
bigText: 'в четыре с четвертью часа четыре чёрненьких чумазеньких чертёнка чертили
чёрными чернилами чертёж.'
},
{
id: 2,
author: 'Просто Вася',
text: 'Считаю, что $ должен стоить 35 рублей!',
bigText: 'А евро 42!'
},
{
id: 3,
// удалили автора
text: 'Прошло 2 года с прошлых учебников, а $ так и не стоит 35',
bigText: 'А евро опять выше 70.'
},
{
id: 4,
author: 'Гость',
text: 'Бесплатно. Без смс, про реакт, заходи - https://maxpfrontend.ru',
bigText: 'Еще есть группа VK, telegram и канал на youtube! Вся инфа на сайте, не р
еклама!'
}
];
Посмотрим результат:
50
Prop-types
Никаких ошибок. Но наше приложение не работает так как надо. Кто виноват? Реакт?
Backend-программист который прислал нам такие данные?
react так и поступил, и показал нам "ничего" (на скриншоте это только "двоеточие").
51
Prop-types
52
Prop-types
Article.propTypes = {
data: PropTypes.shape({
author: PropTypes.string.isRequired,
text: PropTypes.string.isRequired
})
}
53
Prop-types
54
Использование state
Использование state
Вернемся от теории к практике: давайте покликаем по ссылкам-кнопочкам,
поизменяем свойства компонентов...
Упс, не выйдет! Как вы помните, свойства (this.props) следует использовать только для
чтения, а для динамических свойств нужно использовать так называемое "состояние"
(state).
Так как мне нужно в этом разделе сохранить минимум теории и больше практики,
сразу перейдем к делу. Предлагаю вместе решить следующую задачу: у новости есть
ссылка "подробнее", по клику на которую - бинго, текст новости целиком.
55
Использование state
const myNews = [
{
id: 1,
author: 'Саша Печкин',
text: 'В четверг, четвертого числа...',
bigText: 'в четыре с четвертью часа четыре чёрненьких чумазеньких чертёнка чертили
чёрными чернилами чертёж.'
},
{
id: 2,
author: 'Просто Вася',
text: 'Считаю, что $ должен стоить 35 рублей!',
bigText: 'А евро 42!'
},
{
id: 3,
author: 'Max Frontend',
text: 'Прошло 2 года с прошлых учебников, а $ так и не стоит 35',
bigText: 'А евро опять выше 70.'
},
{
id: 4,
author: 'Гость',
text: 'Бесплатно. Без смс, про реакт, заходи - https://maxpfrontend.ru',
bigText: 'Еще есть группа VK, telegram и канал на youtube! Вся инфа на сайте, не р
еклама!'
}
];
Затем, научимся отображать полный текст новости сразу после вводного текста:
56
Использование state
Article.propTypes = {
data: PropTypes.shape({
author: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
bigText: PropTypes.string.isRequired // добавили propTypes для bigText
})
}
Проверим...
57
Использование state
...
return (
<div className='article'>
<p className='news__author'>{author}:</p>
<p className='news__text'>{text}</p>
<a href="#" className='news__readmore'>Подробнее</a>
<p className='news__big-text'>{bigText}</p>
</div>
)
...
Начальное состояние
С состоянием (state) можно работать только в statefull компонентах (class)
58
Использование state
Формализуем задачу:
если значение this.state.visible === false -> рисуй "подробнее", не рисуй "большой
текст";
если же this.state.visible === true -> не рисуй "подробнее", рисуй большой текст;
59
Использование state
Можете проверить в браузере, и покликать на чекбокс внутри state зоны. Шаблон уже
будет реагировать. А мы продолжим делать все это по-человечески, чтобы можно
было кликать на "подробнее".
60
Использование state
Каждый компонент <Article /> имеет свое состояние! Поэтому, при клике на
подробнее в одном из компонентов, только его состояние изменяется, и только у этой
новости отображается полный текст.
61
Использование state
Итого:
62
Использование state
...
state = {
visible: false,
rating: 0,
eshe_odno_svoistvo: 'qweqwe'
}
...
this.setState({rating: 100500})
this.setState({
rating: 100500,
visible: true,
eshe_odno_svoistvo: 'привет'
})
Так же у setState есть возможность указать callback функцию, которая будет вызвана
после того, как новое состояние "установится".
...
readmoreClick: function(e) {
e.preventDefault();
this.setState({ visible: true }, () => {
alert('Состояние изменилось');
});
},
...
Еще setState есть возможность... (на самом деле есть, но с нас пока хватит).
63
Подробнее о state
Подробнее о state
В этом разделе мы посмотрим как изменение state влияет на компонент и немного
"зацепим" stateless архитектуру.
...
render() {
const { author, text, bigText } = this.props.data
const { visible } = this.state
console.log('render', this); // добавили console.log
return (
<div className='article'>
<p className='news__author'>{author}:</p>
<p className='news__text'>{text}</p>
{
!visible && <a onClick={this.handleReadMoreClck} href="#" className='news__rea
dmore'>Подробнее</a>
}
{
visible && <p className='news__big-text'>{bigText}</p>
}
</div>
)
}
...
64
Подробнее о state
65
Подробнее о state
66
Подробнее о state
Попробуйте сами.
Подсказка #1: добавьте свойство state в компонент <News /> для создания начального
состояния.
...
state = {
counter: 0
}
...
67
Подробнее о state
if (data.length) {
newsTemplate = data.map(function(item) {
return <Article key={item.id} data={item}/>
})
} else {
newsTemplate = <p>К сожалению новостей нет</p>
}
return newsTemplate
}
render() {
const { data } = this.props
const { counter } = this.state // вытащили counter
return (
<div className='news'>
{this.renderNews()}
{ /* добавили onClick */
data.length ? <strong onClick={this.handleCounter} className={'news__count'}>
Всего новостей: {data.length}</strong> : null
}
<p>Всего кликов: { counter }</p>
</div>
);
}
}
68
Подробнее о state
В таком случае, мы должны были бы потерять всего 1 клик, не так ли? Проверьте в
консоли следующим образом: откройте вкладку React в консоли, выберите компонент
<News /> , начните кликать на фразу "Всего новостей"
Да, изменяется
69
Подробнее о state
Самое время крикнуть - верните мои деньги назад, и уйти... Но, не все так плачевно.
Теперь вы знаете об этой особенности, и будете если что вооружены. Зачем так
сделано? Вероятно, для оптимизации работы библиотеки в целом.
70
Работа с input
Работа с input
Сперва приберемся:
...
// --- добавили test input ---
class TestInput extends React.Component {
render() {
return (
<input className='test-input' value='введите значение' />
)
}
}
Вообще, код сейчас не работает (но это не из-за комментария). Давайте посмотрим на
ошибку внимательно:
71
Работа с input
72
Работа с input
...
state = { myValue: '' }
...
onChangeHandler = (e) => {
this.setState({ myValue: e.target.value })
},
...
Решение:
73
Работа с input
e.currentTarget vs e.target
Документация, что за свойство currentTarget (MDN)
Представьте ситуацию: у вас будет onClick стоять на div , внутри которого будет текст
в параграфе. В итоге, при клике на текст в паграфе:
Кто вообще не понял о чем идет речь - нужно читать основы про объект события или
весь раздел основы работы с событиями целиком (Кантор).
Попробуйте сами.
74
Работа с input
Подсказка #1:
Вам необходимо:
Подсказка #2:
Так как нам необходимо рендерить больше одного элемента, нужно обернуть их в
родительский элемент, например в <div></div> или React.Fragment
Решение:
css/app.css
...
.test-input {
margin: 0 5px 5px 0;
}
...
75
Работа с input
После добавления отступа в данном коде ничего не раздражает. Или нет? Как думаете,
что здесь может расстроить борца за оптимизацию?
Ответ: каждый раз, после любого изменения у нас вызывается setState, а значит -
полная перерисовка компонента. Не очень приятно. Опять же, чуть больше логики в
момент render'a компонента и в пору будет расстроиться от "отзывчивого" поля ввода.
У нас логики в render-методе никакой нет, поэтому для нас это лишняя оптимизация.
Однако, рассмотреть способ создания "неконтролируемых компонентов" нужно
обязательно.
Начнем по порядку:
76
Работа с input
onBtnClickHandler = () => {
alert(this.input.current.value)
},
77
Работа с input
Исходный код на данный момент (включая alert и console.log). Пока что оставлен input
с ref.
78
Жизненный цикл компонента
Lifecycle methods
У каждого компонента, есть жизненый цикл (lifecycle): компонент будет примонтирован,
компонент отрисовался, компонент будет удален и так далее...
У всех этих фаз есть методы, так называемые lifecycle-methods. Полный список как
всегда в документации. Предлагаю вам в конце урока еще раз его посмотреть, а пока
хватит информации и здесь.
Стартуем:
79
Жизненный цикл компонента
componentWillReceiveProps (nextProps) {
this.setState({
likesIncreasing: nextProps.likeCount > this.props.likeCount
});
}
Обратите внимание: в этот момент, старые props доступны как this.props, а новые
props доступны в виде nextProps аргумента функции.
Так же, если вы вызываете setState внутри этого метода - не будет вызван
дополнительный render.
80
Жизненный цикл компонента
Здесь хочу заострить внимание, что чаще всего в старом коде или старых
туториалах будет попадаться метод componentWillReceiveProps, который Facebook-
команда предлагает заменять на getDerivedStateFromProps.
Итого: главная мысль данного урока: у компонента есть стадии жизни, "в которые
можно писать код". Да, пусть я выступаю здесь как "плохой программист", который
советует вам писать свои велосипеды на разных стадиях жизни компонента, но
именно таким образом вы быстро освоитесь. Ставьте console.log и смотрите когда он
срабатывает. Думайте как это можно использовать в своих целях.
81
Работа с формой
Работа с формой
В данном уроке мы превратим наш input в форму добавления новости. Научимся
работать с чекбоксами, disabled атрибутом кнопки и прочими стандартными для такой
задачи вещами.
Результатом добавления новости, пока что, вновь, будет alert с текстом новости.
Переименуйте <TestInput /> в <Add /> , и рендерите в нем следующую форму: автор
(input), текст новости (textarea), "я согласен с правилами" (checkbox), "показать alert"
(button).
82
Работа с формой
Если вы не против моего оформления, можете взять стили для компонента <Add /> :
83
Работа с формой
.add {
margin: 0 5px 5px 0;
width: 210px;
border: 1px dashed rgba(0, 89, 181, 0.82);
padding: 5px;
}
.add__author, .add__text, .add__btn, .add__checkrule {
display: block;
margin: 0 0 5px 0;
padding: 5px;
width: 94%;
border: 1px solid rgba(0, 89, 181, 0.82);
}
.add__checkrule {
border: none;
font-size: 12px;
}
.add__btn {
box-sizing: content-box;
color: #FFF;
text-transform: uppercase;
background: #007DDC;
}
.add__btn:disabled {
background: #CCC;
color: #999;
}
Задача: сейчас инпут "ваше имя" и text area - просто "болванка". Нужно сделать их
контролируемыми.
Подсказка:
84
Работа с формой
85
Работа с формой
Инпуты начали работать и у нас есть очень приятный бонус: имя и текст новости
хранится в this.state. Удобно? Разумеется. Представьте, что мы будем делать
валидацию формы. У нас в любой момент времени будут актуальные значения
86
Работа с формой
Однако, для начала заставим работать связку чекбокс + кнопка отправки новости.
Давайте отключим кнопку "показать alert", если не отмечен чекбокс. Здесь есть 2
варианта - использовать state или не использовать. Для нашей задачи никаких
проблем с производительностью не будет, если мы будем использовать state. Лазить в
DOM (даже с помощью refs) в React-приложениях - не лучший вариант.
87
Работа с формой
88
Работа с формой
Мне нечего более здесь комментировать для тех, кто знает основы JavaScript. Для тех,
кто не знает:
checked (Кантор);
Изменение: change, input, cut, copy, paste (Кантор)
disabled = true будет означать, что кнопка выключена. Кнопка должна быть
выключена тогда, когда agree = false (то есть, чекбокс не отмечен), значит мы
делаем отрицание (НЕ) с помощью знака восклицания;
Для добавления новости нам осталось сформировать код, который будет выводить в
alert имя и текст новости. Я думаю, эта задача вам точно под силу.
Решение:
89
Работа с формой
...
onBtnClickHandler = (e) => {
e.preventDefault()
const { name, text } = this.state
alert(name + '\n' + text) // \n = перенос строки
}
Почему часть 1? Потому что, валидация форм и вообще работа с формой - одна из
самых скрупулезных задач. Нужно показывать понятные сообщения об ошибках,
подсвечивать неправильно заполненное поле, блокировать кнопку отправки,
валидировать поля по определенным правилам и так далее.
В данный момент, мы добавим к условию (что чекбокс отмечен) только одно простое
условие: поля name и text должны быть заполнены. И не пробелами.
Как бы решалась такая задача без react? Вероятно у нас была бы функция validate,
которая вызывалась бы на каждое изменение в проверяемых полях. Нужно было бы
генерировать и прослушивать событие...
Думаю, вы поняли намек. Здесь без state не обойтись, и это точно то место, где
удобнее использовать именно состояние, а не refs.
Задача: если в поле "имя" или "текст" не введено ничего (либо пробелы) - кнопка
"показать alert" должна быть недоступной.
90
Работа с формой
validate = () => {
// какие то условия
// возвращает true или false
}
...
<button disabled={this.validate()} ... >
...
Решение:
91
Работа с формой
Порефакторим копипасту
Есть проблемка:
92
Работа с формой
Готовы?
93
Работа с формой
94
Добавить новость
Добавить новость
Что такое добавление новости?
угодно), которая будет иметь доступ к state с новостями и которая в свою очередь
будет добавлять новость в этот state.
Так как state у <App /> будет изменяться, все дети (в том числе и новостная лента
<News /> ) будут перерисованы, а следовательно - мы увидим добавленную новость.
render() {
return (
<React.Fragment>
<Add />
<h3>Новости</h3>
{/* считали новости из this.state */}
<News data={this.state.news} />
</React.Fragment>
)
}
}
95
Добавить новость
96
Добавить новость
Add.propTypes = {
onAddNews: PropTypes.func.isRequired, // func используется для проверки передачи fun
ction
}
Проверим:
97
Добавить новость
98
Добавить новость
Проверим?
99
Добавить новость
передаем name).
Задача:
100
Добавить новость
Решение:
<body>
<div id="root"></div>
<script type="text/babel">
const myNews = [
{
id: 1,
author: 'Саша Печкин',
text: 'В четверг, четвертого числа...',
bigText: 'в четыре с четвертью часа четыре чёрненьких чумазеньких чертёнка чер
тили чёрными чернилами чертёж.'
},
{
id: 2,
author: 'Просто Вася',
text: 'Считаю, что $ должен стоить 35 рублей!',
bigText: 'А евро 42!'
101
Добавить новость
},
{
id: 3,
author: 'Max Frontend',
text: 'Прошло 2 года с прошлых учебников, а $ так и не стоит 35',
bigText: 'А евро опять выше 70.'
},
{
id: 4,
author: 'Гость',
text: 'Бесплатно. Без смс, про реакт, заходи - https://maxpfrontend.ru',
bigText: 'Еще есть группа VK, telegram и канал на youtube! Вся инфа на сайте,
не реклама!'
}
];
Article.propTypes = {
data: PropTypes.shape({
id: PropTypes.number.isRequired, // добавили id, это число, обязательно
author: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
bigText: PropTypes.string.isRequired
})
}
102
Добавить новость
if (data.length) {
newsTemplate = data.map(function(item) {
return <Article key={item.id} data={item}/>
})
} else {
newsTemplate = <p>К сожалению новостей нет</p>
}
return newsTemplate
}
render() {
const { data } = this.props
return (
<div className='news'>
{this.renderNews()}
{
data.length ? <strong className={'news__count'}>Всего новостей: {data.le
ngth}</strong> : null
}
</div>
);
}
}
News.propTypes = {
data: PropTypes.array.isRequired
}
103
Добавить новость
104
Добавить новость
Add.propTypes = {
onAddNews: PropTypes.func.isRequired,
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>
</body>
Готово!
105
Итоги по основам
Итоги
Чему вы научились на данный момент:
[1] - разница в том, что stateless компонент, имеет встроенную "легкую" проверку в
shouldComponentUpdate . Она невидимая, но она есть. Этот пункт можно отнести к теме
[2] - этот пункт раскрыт во второй серии (про Redux), но так как Redux-туториал еще не
переписан, я думаю стоит "прокачать" пример с новостями здесь и сейчас.
Так же, есть неудобство, что вы слышали про create-react-app импорты и прочее, а
здесь в руководстве всего этого нет. Конечно, это сделано для того, чтобы руководство
было максимально "сухим" и по теме. Тем не менее, мне бы хотелось добавить это в
обновленной версии.
106
Итоги по основам
107
create-react-app
Create-react-app
Здесь я буду предельно краток: facebook выкатили удобный инструмент для старта
приложения. Поддерживается новый синтаксис, импорты, тестирование, перезагрузка
страницы при изменениях, линтер и многое другое.
Если вы не знакомы с данными командами, значит вам нужно поставить себе node.js и
ввести их в терминале после.
108
create-react-app
.none {
display: none;
}
body {
background: rgba(0, 102, 255, 0.38);
font-family: sans-serif;
}
p {
margin: 0 0 5px;
}
.article {
background: #FFF;
border: 1px solid rgba(0, 89, 181, 0.82);
width: 600px;
margin: 0 0 5px;
box-shadow: 2px 2px 5px -1px rgb(0, 81, 202);
padding: 3px 5px;
}
.news__author {
text-decoration: underline;
color: #007DDC;
}
.news__count {
margin: 10px 0 0 0;
display: block;
}
.test-input {
margin: 0 5px 5px 0;
}
.add {
margin: 0 5px 5px 0;
109
create-react-app
width: 210px;
border: 1px dashed rgba(0, 89, 181, 0.82);
padding: 5px;
}
.add__author, .add__text, .add__btn, .add__checkrule {
display: block;
margin: 0 0 5px 0;
padding: 5px;
width: 94%;
border: 1px solid rgba(0, 89, 181, 0.82);
}
.add__checkrule {
border: none;
font-size: 12px;
}
.add__btn {
box-sizing: content-box;
color: #FFF;
text-transform: uppercase;
background: #007DDC;
}
.add__btn:disabled {
background: #CCC;
color: #999;
}
const myNews = [
{
id: 1,
author: "Саша Печкин",
text: "В четверг, четвертого числа...",
bigText:
"в четыре с четвертью часа четыре чёрненьких чумазеньких чертёнка чертили чёрным
и чернилами чертёж."
},
{
id: 2,
author: "Просто Вася",
text: "Считаю, что $ должен стоить 35 рублей!",
bigText: "А евро 42!"
},
{
id: 3,
author: "Max Frontend",
110
create-react-app
}
];
Article.propTypes = {
data: PropTypes.shape({
id: PropTypes.number.isRequired, // добавили id, это число, обязательно
author: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
bigText: PropTypes.string.isRequired
})
};
111
create-react-app
renderNews = () => {
const { data } = this.props;
let newsTemplate = null;
if (data.length) {
newsTemplate = data.map(function(item) {
return <Article key={item.id} data={item} />;
});
} else {
newsTemplate = <p>К сожалению новостей нет</p>;
}
return newsTemplate;
};
render() {
const { data } = this.props;
return (
<div className="news">
{this.renderNews()}
{data.length ? (
<strong className={"news__count"}>
Всего новостей: {data.length}
</strong>
) : null}
</div>
);
}
}
News.propTypes = {
data: PropTypes.array.isRequired
};
112
create-react-app
113
create-react-app
Add.propTypes = {
onAddNews: PropTypes.func.isRequired
};
// добавился export
export default App;
114
create-react-app
У нас нет PropTypes в проекте. Так как раньше это был тэг script, а теперь нам нужен
npm-пакет.
Я не привожу сюда примеров для yarn, так как если у вас yarn вы итак в курсе, как
ставить пакеты через него.
src/App.js
115
create-react-app
npm start
116
Приборка и импорты
Приборка
Запустите приложение, если оно у вас не запущено ( npm start ).
./src/App.js
Line 50: Links must not point to "#". Use a more descriptive href or use a button
instead jsx-a11y/href-no-hash
Line 126: 'value' is assigned a value but never used
no-unused-vars
Line 140: 'agree' is assigned a value but never used
no-unused-vars
Строка 50: у ссылки должен быть атрибут href не #, а что-то более вразумительное
(замените на '#readmore').
// было
const { id, value } = e.currentTarget
this.setState({ [id]: e.currentTarget.value })
// стало
const { id, value } = e.currentTarget
this.setState({ [id]: value })
// было
const { name, text, bigText, agree } = this.state
// стало
const { name, text, bigText } = this.state
117
Приборка и импорты
Импорты
Наша задача - разбить огромный файл src/App.js на компоненты.
118
Приборка и импорты
src/components/Article.js
119
Приборка и импорты
import React from 'react' // мы обязаны импортировать необходимые пакеты в каждом файле
Article.propTypes = {
data: PropTypes.shape({
id: PropTypes.number.isRequired, // добавили id, это число, обязательно
author: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
bigText: PropTypes.string.isRequired,
}),
}
120
Приборка и импорты
src/App.js
const myNews = [
{
id: 1,
author: 'Саша Печкин',
...
src/components/News.js
121
Приборка и импорты
Я думаю принцип понятен: в каждом файле мы импортируем то, что нам нужно
относительно этого файла.
122
Приборка и импорты
[
{
"id": 1,
"author": "Саша Печкин",
"text": "В четверг, четвертого числа...",
"bigText":
"в четыре с четвертью часа четыре чёрненьких чумазеньких чертёнка чертили чёрным
и чернилами чертёж."
},
{
"id": 2,
"author": "Просто Вася",
"text": "Считаю, что $ должен стоить 35 рублей!",
"bigText": "А евро 42!"
},
{
"id": 3,
"author": "Max Frontend",
"text": "Прошло 2 года с прошлых учебников, а $ так и не стоит 35",
"bigText": "А евро опять выше 70."
},
{
"id": 4,
"author": "Гость",
"text": "Бесплатно. Без смс, про реакт, заходи - https: //maxpfrontend.ru",
"bigText":
"Еще есть группа VK, telegram и канал на youtube! Вся инфа на сайте, не реклама!"
}
]
src/App.js
123
Приборка и импорты
Исходный код.
124
Асинхронные запросы
Асинхронные запросы
Нам все еще не нужен redux, ничего подобного.
CRA так устроен, что если вы положите что-нибудь в public директорию, это будет
доступно по пути:
Конечно, при этом у нас сломался импорт в App.js (так как такого файла по старому
пути нет):
125
Асинхронные запросы
Так как у нас есть доступ к файлу через GET-запрос, мы можем запросить его.
У нас есть данные на сервере (новости в json), нам нужно их запросить и отобразить в
списке. Пока запрос выполняется, мы хотим показывать юзеру надпись: "Загружаю..."
вместо списка новостей, чтобы он не нервничал. Когда новости загружены - мы хотим
отобразить их как раньше.
[1] - это вопрос про нативный js. Запрос будем делать с помощью fetch.
src/App.js
126
Асинхронные запросы
Второе условие:
src/App.js
return (
<React.Fragment>
<Add onAddNews={this.handleAddNews} />
<h3>Новости</h3>
{isLoading && <p>Загружаю...</p>}
{Array.isArray(news) && <News data={news} />}
</React.Fragment>
)
}
}
127
Асинхронные запросы
Помните, мы с вами один раз описали, что количество новостей отображает цифры в
зависимости от данных и когда стали добавлять новости - мы это место вообще не
трогали, но счетчик работал корректно. Это декларативный подход. Так же и сейчас -
мы в силу того, что я вижу всю картину, описали шаблон и как ему себя вести, а
состояние разруливать будем на последнем шаге. Такой трюк может быть недоступен
вам некоторое время, пока вы обучаетесь, поэтому пишите код как будет удобно,
например делайте шаг за шагом что-то и воюйте с ошибками, главное - практика.
src/App.js
128
Асинхронные запросы
129
Асинхронные запросы
Рассказывать про то как работает promise я не буду, но если у вас есть вопросы, вот
мои любимые материалы:
Promise (Кантор)
У нас проблемы с промисами (перевод статьи на хабре)
(так как решение в пару строк, я сделаю отступ. Очень хочу чтобы вы попробовали
сами)
. . . . . .
Решение:
src/App.js
130
Асинхронные запросы
...
componentDidMount() {
// ставим isLoading true,
// то есть запрос за даннмыи начался
// фактически он начнется в строке с fetch,
// но на переход от одной строки к другой
// пройдут миллисекунды
this.setState({ isLoading: true })
fetch('http://localhost:3000/data/newsData.json')
.then(response => {
return response.json()
})
.then(data => {
// запрос завершился успешно,
// делаем isLoading: false
// в news кладем пришедшие данные
this.setState({ isLoading: false, news: data })
})
}
...
Что интересно, у нас опять все работает. Мы не трогали компонент <News /> , так как
мы вновь просто изменили "источник данных".
src/App.js
...
componentDidMount() {
this.setState({ isLoading: true })
fetch('http://localhost:3000/data/newsData.json')
.then(response => {
return response.json()
})
.then(data => {
setTimeout(() => { // добавили задержку
this.setState({ isLoading: false, news: data })
}, 3000) // в три секунды
})
}
...
131
Асинхронные запросы
Подождите три секунды и увидите как появится список новостей. Причем, что хочется
отметить - опять нам помогает React. Изменился state -> вызвался render. Никаких
дополнительных манипуляций ;)
И никакого Redux/Mobx и прочего. Задача решена без "дичи" в виде кучи библиотек,
которые здесь не уместны. Поздравляю.
Исходный код.
132
Спам-фильтр
Делаем спам-фильтр
Мне осталось осветить момент обновления данных. Ранее обработка происходила в
componentWillReceiveProps , а сейчас в getDervidedStateFromProps (еще и static).
Для этого мне пришлось выдумать задачу, которая на самом деле решается в момент
валидации новости на бэкэнде. Но представим, что наш бэкэндер очень занят, а
менеджер говорит - пожалуйста, сделай как-нибудь, потом доделаем (ага!).
133
Спам-фильтр
134
componentWillReceiveProps
ComponentWillReceiveProps (CWRP)
Давайте начнем со старого метода жизненного цикла (componentWillReceiveProps),
который будет поддерживаться до React 17й версии. Нам нужно это знать, потому что
много (очень много) кода уже написано и вам, наверняка, достанется такой проект.
src/components/News.js
...
export { News }
135
componentWillReceiveProps
Одно НО, так как мы хотим что-то изменять, значит у нас у компонента появляется
состояние! Обращаю внимание: данная задача сейчас решается не оптимальным
способом, мы просто учимся следующим моментам:
src/components/News.js
136
componentWillReceiveProps
...
class News extends React.Component {
componentWillReceiveProps(nextProps) {
console.log({ nextProps })
console.log({ oldProps: this.props })
}
renderNews = () => {
const { filteredNews } = this.state // используем состояние
let newsTemplate = null
return newsTemplate
}
render() {
const { filteredNews } = this.state // аналогично, используем состояние
return (
<div className="news">
{this.renderNews()}
{filteredNews.length ? (
<strong className={'news__count'}>
Всего новостей: {filteredNews.length}
</strong>
) : null}
</div>
)
}
}
...
Внутри CWRP можно безопасно использовать setState, так как это не приведет к
дополнительной (лишней) перерисовке.
src/components/News.js
137
componentWillReceiveProps
componentWillReceiveProps(nextProps) {
let nextFilteredNews = [...nextProps.data]
В данном фрагменте ничего необычного, для тех кто в теме основ (я понимаю, что
фраза "знать основы" набила оскомину, но их действительно нужно знать).
toLowerCase (MDN)
indexOf (MDN)
Итого:
Исходный код
138
getDerivedStateFromProps
static getDerivedStateFromProps
Сигнатура метода: static getDerivedStateFromProps(props, state) и документация, в
которой описано, что данный метод нужен для очень редких случаев. Мы сами
выдумали себе задачу и ограничения для решения, поэтому он нам пригодится.
src/components/News.js
...
static getDerivedStateFromProps(props, state) {
console.log(props)
console.log(state)
return {
filteredNews: props.data,
}
}
// удалите componentWillReceiveProps
...
139
getDerivedStateFromProps
src/components/News.js
140
getDerivedStateFromProps
Исходный код
141
Порефакторим...
Порефакторим
В течении всего учебника я учил вас думать о данных, а потом в CWP и gDSFR взял и
сделал из образно stateless (хоть он и был через class - состояния у него не было)
компонента - statefull. Это было сделано для удобства объяснений.
Смотрите, если мы откатим наш <News /> на два урока назад (когда компонент просто
получал props), то мы сможем в <App /> использовать gDSFR и там "рубить спам".
Таким образом, мы бы опять решили задачу без изменения stateless компонента.
Было: компонент <News /> умел отображать данные. Стало: компонент <News />
умеет отображать данные и помечать спам.
Задача: обрабатывать данные в <App /> , вернуть <News /> к прежнему "тупому"
образу жизни.
Подсказка: вы запросто можете сделать все что нужно в <App /> , так как мы только
что отработали этот прием.
src/components/News.js
142
Порефакторим...
if (data.length) {
newsTemplate = data.map(function(item) {
return <Article key={item.id} data={item} />
})
} else {
newsTemplate = <p>К сожалению новостей нет</p>
}
return newsTemplate
}
render() {
const { data } = this.props
return (
<div className="news">
{this.renderNews()}
{data.length ? (
<strong className={'news__count'}>
Всего новостей: {data.length}
</strong>
) : null}
</div>
)
}
}
News.propTypes = {
data: PropTypes.array.isRequired,
}
export { News }
Решение
Полный код компонента <App />
src/App.js
143
Порефакторим...
return {
filteredNews: nextFilteredNews,
}
}
return null
}
componentDidMount() {
this.setState({ isLoading: true })
fetch('http://localhost:3000/data/newsData.json')
.then(response => {
return response.json()
})
.then(data => {
setTimeout(() => {
this.setState({ isLoading: false, news: data })
}, 1000) // изменил таймер на 1000, чтобы не ждать долго
})
}
handleAddNews = data => {
const nextNews = [data, ...this.state.news]
this.setState({ news: nextNews })
}
render() {
const { news, isLoading } = this.state
return (
<React.Fragment>
<Add onAddNews={this.handleAddNews} />
144
Порефакторим...
<h3>Новости</h3>
{isLoading && <p>Загружаю...</p>}
{Array.isArray(news) && <News data={news} />}
</React.Fragment>
)
}
}
Итого:
Исходный код
145
Заключение
Заключение
Что сказать? Вы молодцы. Основы React'a у вас в голове. Практиковались пока
читали? Если нет - еще раз прочитайте + задачки поделайте.
Если да - welcome (добро пожаловать). Дальше больше: реакт не заботится о том, как
вы будете хранить данные в вашем приложении. Гонять все из state родителя вниз
через props'ы не удобно.
146