В компонентном подходе компонентны не только рендер и стили, но и логика
iframe: https://www.youtube.com/embed/f11f8ksArp4
iframe: https://www.youtube.com/embed/eQyGf8Fe_YQ
iframe: https://www.youtube.com/embed/tj3wrvHdyuo
Окей, с вёрсточкой разобрались: поняли, как верстать компонентами, переехали с селекторов на Стайлед-компонентс, поиграли с Реакт-роутером, но пора двигаться дальше.
Где хранить данные? Как с ними работать? Нужно ли писать сервис-провайдеры-фабрики как в Ангуларе?
Слава богу, не нужно! Реакт несложный даже в работе с данными и оперирует обычными функциями, которые возвращают новые данные.
Как работает Реакт? У компонента есть стейт — состояние данных, есть рендер — вёрстка, в сумме это то самое уравнение ui = render(state)
из первого урока.
Что это значит? Когда обновляется стейт, вызывается рендер и строится новый интерфейс.
Да, само. Да, нужно всего лишь обновить стейт, рендер сам вызовется. Да, это так удобно.
Да, если вы хотите поставить кнопке красный цвет, не нужно писать document.getElementById("my-button").style.background = 'red'
, достаточно просто поставить новый стейт.
Да, если вы хотите чекбоксу поставить checked
, можно в стейт положить checked: true
и передать пропом checked={state.checked}
.
Да, в стейте можно хранить даже данные с сервера.
Да, в стейте хранят и данные заполненных форм.
Да, это работает как на маленьких проектах, так и на крупных.
Да, Редакс уже близко. Редакс это всего лишь глобальный стейт, а не компонентный.
Что такое стейт? Это просто объект с данными, к которому можно обратиться. Давайте в виде псевдокода представим что у нашего компонента есть стейт.
const state = {};
// рендерим
function Movies() {
return (
<div>
<p>Sort by:</p>
<button>alphabetical</button>
<button>date released</button>
<button>reviews count</button>
<button>average rating</button>
...movies list
</div>
);
}
В этом стейте мы можем хранить данные. Почему в стейте, а не просто константой? Стейт — для динамических данных, а в интерфейсе у нас есть сортировка фильмов.
Обновилась сортировка → обновился список фильмов → обновился интерфейс.
Каждой кнопке мы добавим проп onClick
, внутри которого будет функция, которая будет вызывать нашу функцию handleClick
и передавать туда поле, по которому нужно отсортировать. Заодно наполним стейт начальными данными.
const state = {
movies: [
{
id: 1,
title: "City Lights",
year: 1931,
rating: 8.5
},
{
id: 2,
title: "American History X",
year: 1998,
rating: 8.5
},
{
id: 3,
title: "Wild Strawberries",
year: 1957,
rating: 8.1
}
]
};
function changeSort(kind) {}
// рендерим
function Movies() {
return (
<div>
<p>Sort by:</p>
<button onClick={() => changeSort("alphabetical")}>alphabetical</button>
<button onClick={() => changeSort("date")}>date released</button>
<button onClick={() => changeSort("rating")}>average rating</button>
...movies list
</div>
);
}
Теперь давайте представим, что у нас есть некая магическая функция setState
, которая принимает в себя данные и обновляет стейт.
Причем, что удобно, обновляет только указанные данные: например, если у вас в стейте три поля isFetching, movies, error
, а вы вызовете setState({ isFetching: true })
, то movies
и error
останутся такими же, какими и были.
Окей, у нас есть эта функция, давайте теперь напишем нашу функцию changeSort()
:
// заведём функцию сортировки
// https://davidwalsh.name/array-sort
function sortObjectsByField(field, leftObject, rightObject) {
return leftObject[field] - rightObject[field];
}
function changeSort(kind) {
// заведём объект,
// который будет приводить
// kind к полю у объекта
const sortFieldsToKey = {
alphabetical: "title",
date: "year",
rating: "rating"
};
// получим поле у объектов,
// по которому нужно сортировать
const currentSortField = sortFieldsToKey[kind];
// сохраним текущее поле
// чтобы подсветить кнопку активным
// ЭТА ФУНКЦИЯ ПРИДУМАНА, ЕЁ НЕ СУЩЕСТВУЕТ
setState({ orderField: kind });
// отсортируем фильмы
setState({
movies: state.movies.sort(function(leftObj, rightObj) {
return sortObjectsByField(currentSortField, leftObj, rightObj);
})
});
}
Что дальше? В рендере мы просто выведем эти фильмы через метод .map()
у массива.
Этот метод проходится по массиву, на каждом элементе вызывает функцию, которая должна вернуть новое значение под этим же индексом массива, в нашем случае — Реакт-компонент.
Если тут ничего непонятно — перечитайте третий урок про базовый джаваскрипт.
function Movies() {
return (
<div>
<p>Sort by:</p>
<button onClick={() => changeSort("alphabetical")}>alphabetical</button>
<button onClick={() => changeSort("date")}>date released</button>
<button onClick={() => changeSort("rating")}>average rating</button>
{state.movies.map(function(movie) {
// movie это объект с данными
// возвращаем Реакт-компонент
return (
<a href={`/movies/${movie.id}`} key={movie.id}>
{movie.title} ({movie.year}) | rating {movie.rating}
</a>
);
})}
</div>
);
}
Что ещё за фигурные скобки? Это Джсх, в фигурных скобках может быть любое Джс-выражение: хоть
2 + 2
, хоть обращение к объектуuser.firstName
, хоть вызов функцииformatName(user)
.
Что за проп
key
? Это обязательный у списков проп, который помогает Реакту отделить один компонент от другого при обновлении данных. Реакт справится и без них, но лучше с ними.
Мы же прошлись по массиву и получили новый массив, в котором есть Реакт-компоненты. Их мы и отрендерили.
Если упрощать до обычного Джаваскрипта, мы могли бы новый массив закрепить за константой и обратиться к ней в Джсхе:
function Movies() {
const movies = state.movies.map(function(movie) {
// movie это объект с данными
// возвращаем Реакт-компонент
return (
<a href={`/movies/${movie.id}`} key={movie.id}>
{movie.title} ({movie.year}) | rating {movie.rating}
</a>
);
});
return (
<div>
{/* кнопки сортировки */}
{movies}
</div>
);
}
В итоге мы будем рендерить список фильмов, а при клике на сортировку — сортировать его и автоматически обновлять стейт, потому что у нас есть вымышленная функция setState()
: от неё зависит Реакт и обновляет рендер сразу же после того как обновится стейт.
На этом наш урок подходит к концу.
Сегодня мы разобрались с тем, как у компонентов могут быть стейты, в которых хранятся и обновляются данные через setState()
.
Важно помнить, что всю работу по обновлению интерфейса Реакт берёт на себя, наша задача — писать стейт, обновлять его и описать в рендере вёрстку от этого стейта.
Кстати, а как быть, если хочется не ссылку рендерить через <a>
, а свой компонент? Как туда передать стейт?
Пропами!
function Movie(props) {
return (
<a href={`/movies/${props.id}`}>
{props.title} ({props.year}) | rating {props.rating}
</a>
);
}
function Movies() {
const movies = state.movies.map(function(movie) {
// movie это объект с данными
// возвращаем Реакт-компонент
return (
<Movie
id={movie.id}
year={movie.year}
rating={movie.rating}
key={movie.id}
>
{movie.title}
</Movie>
);
});
return (
<div>
{/* кнопки сортировки */}
{movies}
</div>
);
}