React Junior
207 subscribers
37 photos
462 links
Изучение React с нуля
加入频道
Redux Essentials 3. Страница статьи

Добавляем возможность просмотреть отдельную статью.

https://codesandbox.io/s/redux-essentials-single-post-page-react-junior-3dziwq?file=/src/App.js

Для этого понадобится новый роут вида /posts/123 и компонент SinglePostPage (лежит в папке features/posts).

Роут будет динамический (с параметром - /posts/:id), в компонент нужно получить сначала значение этого параметра, чтобы узнать, с какой статьей мы имеем дело (хук useParams из react-router). А затем - получить данные статьи из хранилища с помощью селектора (getPostData).

Помним, что селектор должен лежать рядом со слайсом, так как он знает, как получать данные из хранилища. В руководстве селекторы объявляются в компонентах.

Если статья с указанным идентификатором не нашлась, выводим сообщение.

Наконец, добавляем ссылки на посты в компоненте PostsList.

#redux #управлениесостоянием #документация #примерыкода
👍3
Redux Essentials 4. Редактирование статьи

https://codesandbox.io/s/redux-essentials-edit-post-react-junior-hbfrh5?file=/src/App.js

Чтобы иметь возможность редактировать статью, нам понадобится:

- новый роут /editPost/:id
- компонент формы EditPostForm
- новый экшен postUpdated, чтобы внести изменения в данные

1) В компоненте получаем id поста из урла с помощью useParams.
2) Затем получаем данные поста с помощью селектора getPostData и подставляем их в форму.
3) Все состояние формы хранится локально.
4) При отправке формы диспатчим экшен с помощью креатора postUpdated, передавая ему всю необходимую информацию.
5) Переходим на роут поста, чтобы увидеть изменения (хук useNavigate).

Так как формы создания и редактирования очень похожи (фактически, это одна и та же форма), появилось большое искушение вынести ее в отдельный компонент - /components/PostForm.js. Он немного избыточен, включает в себя помимо формы еще и текст заголовка и общие стили контейнера, но почему бы и нет.

Что касается экшенов postAdded, postUpdated. Тут как будто напрашивается нормализованное состояние и createEntityAdapter. Посмотрим, что будет дальше.

#redux #управлениесостоянием #документация #примерыкода
👍4
Redux Essentials 5. Авторы постов

Следующий шаг - добавляем в приложение пользователей, ведь статьи не сами собой пишутся.

И тут становится понятно, что решение вынести форму создания/редактирования статьи в отдельный компонент было слишком поспешным. В форму создания нужно будет внести изменения, поэтому теперь это два разных компонента, как и было задумано в руководстве.

https://codesandbox.io/s/redux-essentials-users-slice-react-junior-52ltvw?file=/src/App.js

Что делаем?

1. Добавляем новую фичу users и новый слайс usersSlice. Здесь хранится простой список пользователей, даже никаких экшенов нет (пока по крайней мере). Подключаем слайд к хранилищу в файле store.js. Сразу создаем два селектора - для получения всего списка и для получения отдельного пользователя по идентификатору.

2. В форму создания поста добавлям селект для выбора автора (это сделано для простоты и наглядности, в реальном приложении обычно есть механизмы для определения активного пользователя). Список пользователей получаем с помощью useSelector.

3. Немного изменяем экшен posts/postAdded. Теперь он получает три отдельных параметра вместо одного объекта, поэтому оформляем его в виде массива с полями reducer и prepare.

4. Создаем отдельный компонент /posts/PostAuthor.js, который будет получать id пользователя и выводить его имя.

5. Наконец, подключаем этот компонент в каждый пост в ленте PostsList и в компонент SinglePostPage.

#redux #управлениесостоянием #документация #примерыкода
👍3
Redux Essentials 6. Дата поста

Следующий шаг очень простой и мало связанный, на самом деле, с Redux - добавляем даты создания статей.

https://codesandbox.io/s/redux-essentials-dates-react-junior-y8f6x8?file=/src/App.js

1. Вносим изменения в слайс: добавляем постам новое поле date, устанавливаем текущую дату при создании поста. На прошлом шаге мы разделели редьюсер postAdded на reducer и prepare. Вот как раз в prepare и добавляется этот новый "подготовительный" функционал.

Важно: Так как в хранилище должны находиться только сериализуемые данные, вместо даты записываем туда строковый timestamp.

2. Создаем отдельный компонент TimeAgo для вывода даты (в формате 5 минут назад). Для форматирования используется библиотека date-fns.

3. Выводим дату в списке статей и на странице отдельного поста.

4. В списке дополнительно сортируем статьи по дате, выводя более свежие первыми. Теперь можно добавить новую статью в форме создания, и она окажется первой в списке.

#redux #управлениесостоянием #документация #примерыкода
👍1
Redux Essentials 7. Реакции

Добавляем реакции к статьям.

https://codesandbox.io/s/redux-essentials-reactions-react-junior-1p1k32?file=/src/App.js

1. У каждой статьи появляется поле reactions с набором доступных реакций и счетчиком для каждой реакции.
2. Добавляется новый экшен reactionAdded, который увеличивает счетчик выбранного смайлика.
3. Добавляется компонент ReactionButtons - набор кнопок с разными реакциями.

Механизм реакций, конечно, работает неправильно - сейчас можно ставить сколько угодно лайков, тогда как в реальных приложениях обычно только по одному. Для этого нужно дополнительно сохранять информацию о том, какой юзер что лайкнул. Возможно, это будет реализовано позже.

#redux #управлениесостоянием #документация #примерыкода
👍2
Forwarded from Cat in Web
Легковесные альтернативы Redux

Статья (англ.): https://blog.openreplay.com/lightweight-alternatives-to-redux

Автор статьи рассказывает, какие инструменты можно использовать для управления глобальным состоянием приложения вместо Redux.

1. Хуки React

Простейший вариант - использовать встроенные хуки. С помощью useReducer мы выносим состояние приложения в отдельный объект и можем удобно им управлять, а с помощью контекста доставляем этот объект до всех заинтересованных компонентов. Пример.

2. Zustand

Zustand использует те же flux-принципы, что и Redux. Мы создаем хранилище с помощью функции create и получаем из нее хук useStore, который можно использовать в компонентах.

3. Jotai

Разработан той же командой, что и Zustand, но использует другой подход - атомарный (atomic). Каждая часть данных оформляется в виде атома с помощью функции atom. Для работы с атомами есть хук useAtom, который работает так же, как useState.

4. Valtio

Подход Valtio базируется на использовании прокси. Мы передаем объект в функцию proxy и дальше можем его мутировать - прокси отслеживает все изменения.

В статье есть примеры кода для всех этих решений, можно сформировать общее впечатление и заинтересоваться чем-нибудь.

#redux #react #statemanagement #library
👍2
Forwarded from Cat in Web
React World

Развлекалочка на React: https://sfatihk.github.io/react-world/
Скроль страницу и совершай небольшое путешествие с героями любимых фильмов.

А потом можно залезть в исходники и посмотреть, как все это работает: https://github.com/sfatihk/react-world

#react
👍2
Redux Essentials 8. Запрос данных

Теперь сделаем наше приложение более реалистичным - установим связь с "сервером". В настоящих приложениях данных приходят из базы, а не хранятся на клиенте, поэтому нам нужно их получать и отправлять.

https://codesandbox.io/s/redux-essentials-fetch-posts-react-junior-kx3out?file=/src/App.js

В руководстве используется специальный фейковый REST API с сервером и клиентом, но я не буду так заморачиваться, сделаю простую имитацию с setTimeout.

Thunks

Хранилище работает синхронно, в редьюсерах не должно быть асинхронной логики. Поэтому мы выносим ее в thunks (санки).

Санки - это функции, которые имеют доступ к хранилищу (через аргументы). Они могут выполнять любые асинхронные действия и в нужный момент диспатчить что-нибудь в хранилище.

Мы работаем с санками как с экшенами, передаем их в метод dispatch, а чтобы все правильно обрабатывалось, нужно использовать миддлвар redux-thunk.

createAsyncThunk

Для создания санков используем метод createAsyncThunk. Передаем ему префикс для экшенов и функцию с логикой асинхронного запроса, которая должна возвращать промис. Под капотом createAsyncThunk диспатчит экшен перед началом выполнения и сразу после выполнения наше функции (может быть удачный или ошибочный).

Итак, thunk creator у нас есть и экшены, связанные с ним, тоже. Но хранилище пока ничего не знает об этих экшенах и не умеет их обрабатывать. Добавляем редьюсеры для них с помощью метода extraReducers.

extraReducers это функция, которая в качестве аргумента получает builder. Вот в этот билдер и нужно добавить обработчики для новых экшенов (метод builder.addCase).

Состояние и отображение

Немного меняем состояние, теперь в нем есть поле items для хранения списка постов, а также поля status и error для хранения состояния запроса. Вносим изменения в селекторы, так как путь к данным изменился. Добавляем также новый селектор getStatus.

Список items изначально пустой, чтобы его заполнить, нужно запросить данные "с сервера". Делаем это в компоненте PostsList - внутри хука useEffect.

Кроме того, в этом же компоненте с помощью селектора извлекаем текущий статус запроса. Если данные еще грузятся, выводим лоадер, чтобы пользователь знал, что происходит.

#redux #управлениесостоянием #документация #примерыкода
👍1
Хорошие практики Redux

На базе предыдущего шага:

1. Инкапсуляция логики извлечения данных внутри селекторов. Структура хранилища изменилась, и если бы не было селекторов, пришлось бы вносить изменения во все компоненты, которые получают данных из хранилища.

2. Состояние запроса хранится в виде строки из перечисления (enum).

#redux #управлениесостоянием #паттерны
👍1
Еще раз про createAsyncThunk

Это функция для создания thunk creators. Используем, если нужны экшены начала и конца запроса.

Без createAsyncThunk:

// action creators
const getFetchPostsPending = function() {
type: 'posts/fetchPosts/pending'
};
const getFetchPostsFulfilled = function(posts) {
type: 'posts/fetchPosts/fulfilled',
payload: posts
};
const getFetchPostsRejected = function(error) {
type: 'posts/fetchPosts/rejected',
error
};

// thunk creator
const fetchPosts = function(params) {
return async function (dispatch) {
dispatch(getFetchPostsPending());
try {
const posts = await fakeApi.getPosts(params);
dispatch(getFetchPostsFulfilled(posts))
} catch (err) {
dispatch(getFetchPostsRejected(err.toString()))
}
}
}

// обработка
const postsReducer = function(state, action) {
switch(action.type) {
case 'posts/fetchPosts/pending':
// ...
case 'posts/fetchPosts/fulfilled':
// ...
case 'posts/fetchPosts/rejected':
// ...
}
}

С createAsyncThunk:

// thunk creator
export const fetchPosts = createAsyncThunk("posts/fetchPosts", async function() {
const response = await fakeApi.getPosts();
return response.data;
});

// обработка
const postsSlice = createSlice({
// ...
extraReducers(builder) {
builder
.addCase(fetchPosts.pending, function(state, action) { })
.addCase(fetchPosts.fulfilled, function(state, action) { })
.addCase(fetchPosts.rejected, function(state, action) { });
}
})

#redux #управлениесостоянием #примерыкода
👍1
Builder

builder позволяет добавлять в хранилище дополнительные редьюсеры - для дополнительных экшенов, которые генерируются где-то вне createSlice.

У него есть три метода:

- builder.addCase - для конкретного типа экшена. Первым аргументом нужно передать либо action creator, либо строку с типом экшена.
- bulder.addMatcher - первым аргументом принимает функцию-матчер. В эту функцию передаются все экшены, редьюсер срабатывает, если матчер вернет true.
- builder.addDefaultCase - принимает только редьюсер, который срабатывает, если не сработал ни один другой редьюсер.

#redux #управлениесостоянием #документация
👍2
Redux Essentials 9. Запрос пользователей

По аналогии с запросом статей делаем запрос пользователей.

https://codesandbox.io/s/redux-essentials-fetch-users-react-junior-ipw0r8?file=/src/App.js

1. Пишем thunk creator с помощью createAsyncThunk.
2. Добавляем редьюсер для экшена fetchUsers.fulfilled.
3. Диспатчим новый санк в файле index.js, там где импортируем объект хранилища.

#redux #управлениесостоянием #документация #примерыкода
👍1
Redux Essentials 10. Сохранение постов

Добавляем еще один thunk creator addNewPost (с помощью createAsyncThunk), который будет выполнять запрос к фейковому серверу, отправлять данные из формы и получать обратно объект нового поста (с уникальным id). Создание id происходит на сервере, как это обычно и бывает в реальных приложениях.

https://codesandbox.io/s/redux-essentials-create-post-react-junior-cbfh4u?file=/src/App.js

Убираем редьюсер postAdded и вместо него добавляем новый кейс в extraReducers - для экшена addNewPost.fulfilled.

Для загрузки постов мы добавили в слайс поле status и отслеживали статус запроса, чтобы отображать лоадер. Здесь пойдем другим путем - будем отслеживать статус прямо в компоненте (подробнее - возвращение промиса из thunk). Пока идет запрос, заблокируем отправку формы.

await dispatch(addNewPost({ title, content, user: userId })).unwrap()

Обратите внимание на метод unwrap() у результата выполнения функции dispatch. Его добавляет Redux Toolkit. Если промис завершился неудачно, этот метод выбросит ошибку, которую мы сможем поймать в try...catch

#redux #управлениесостоянием #документация #примерыкода
👍1
Redux Essentials 11. Список авторов и страница автора

На следующем этапе добавляем страницу со списком пользователей и страницу отдельного пользователя со списком статей.

https://codesandbox.io/s/redux-essentials-users-list-user-page-react-junior-xmtqds?file=/src/App.js

Ничего нового:

1) Новый селектор getUserPosts в postsSlice
2) Новый компонент UsersList (использует селектор getUsers)
3) Новый компонент UserPage (использует хук useParams и селектор getUserPosts)
4) Новые роуты /users и /users/:userId в App.js
5) Ссылка на страницу авторов в шапке сайта + ссылки на авторов в превью статей и на странице статьи

#redux #управлениесостоянием #документация #примерыкода
👍1
Forwarded from Cat in Web
React 18

Перевод оригинальной статьи на Хабре: https://habr.com/ru/post/659537/

Обзор новинок последнего релиза - а их немало.

Конкурентный рендеринг

Кардинально изменилась модель рендеринга, появилась конкурентность - нечто подкапотное, что позволяет подготавливать несколько версий интерфейса одновременно. Рендеринг теперь может прерываться, откладываться (раньше был синхронным). Благодаря этому основной поток не блокируется и приложение может реагировать на действия пользователя, даже если за кулисами рендерится что-то огромное.

Появилось несколько новых хуков для работы с новым функционалом, однако в перспективе команда React ожидает, что они будут использоваться в основном для интеграции библиотек с поддержкой конкурентности, а не напрямую. Например, библиотекой маршрутизации для перехода между страницами.

Автоматическое пакетирование

Или группировка нескольких обновлений в один рендеринг. Раньше пакетировались только обновления внутри обработчиков событий React, теперь пакетируются еще и обновления внутри промисов, таймеров, нативных обработчиков и пр.

Переходы (transitions)

Обновления теперь делятся на срочные (немедленная реакция на действие пользователя, например, ввод текста) и переходные (не срочные). Последние - это переход интерфейса из одного состояния в другое (например, появление списка результатов после применения фильтров).

Новая функция startTransition (или хук useTransition) позволяет явно указать "не срочные" обновления. Их выполнение может быть прервано, если в процессе поступят какие-то "срочные" обновления.

Suspense

Расширились возможности React.Suspense. Раньше он только позволял лениво загружать код с помощью React.lazy и не поддерживался при серверном рендеринге. Теперь работает и на сервере тоже и хорошо сочетается с API переходов.

Новые API рендеринга

Новые методы для рендеринга - createRoot и hydrateRoot`на клиенте, `renderToPipeableStream и renderToReadableStream на сервере.

Двойное монтирование компонентов

В строгом режиме в dev mode добавлена проверка на "устойчивость к многократному монтированию" - компоненты в первый раз монтируются, сразу размонтируются с сохранением состояния и монтируются снова. Это нужно для будущих улучшений.

Новые хуки

- useId - генерация уникальных идентификаторов
- useTransition - позволяет пометить обновления как "переходные" (см. выше)
- useDeferredValue - откладывает не срочный перерендеринг
- useSyncExternalStore - хук для библиотек, работающих с внешним состоянием
- useInsertionEffect - хук для библиотек CSS-in-JS для инъекции стилей, срабатывает до layout effect.

#react
👍1
Что мне не нравится в react-router

Статья (рус.): https://habr.com/ru/post/599347/

Мнение разработчика о недостатках React Router. Местами субъективно (точка зрения относительно больших сложных проектов), но в любом случае интересно и полезно для ознакомления. Претензии в основном к старой версии библиотеки, в последнем релизе появилось много полезных фич.

Главные претензии:

1. JSX плохо подходит для описания конфига роутинга. Удобнее работать с обычным JSON.
2. Отсутствие модульности, сложности с формированием сложных ссылок "от корня".
3. Лишние перерендеры одинаковых компонентов в разных роутах.

В комментариях тоже интересно.

#роутинг #ссылки
👍1
Redux Essentials 12. Уведомления

Добавляем функционал уведомлений - страницу со списком и возможность обновить список (получить "новые" уведомления), нажав на кнопку.

https://codesandbox.io/s/redux-essentials-notifications-react-junior-qmk9tf

Так как это просто демка Redux, уведомления будут довольно куцые.

API

В фейковый апи добавлен метод для генерации рандомных сообщений. Он получает время последнего сообщения и создает несколько "новых" сообщений.

Слайс

Добавляем новую фичу features/notifications и создаем новый слайс, который нужно будет добавить в метод configureStore в app/store.js.

Этот фрагмент состояния представляет собой простой массив уведомлений, никакие статусы тут отслеживаться не будут.

Экшен всего один - allNotificationsRead. У каждого уведомления будет статус "прочитано/не прочитано", чтобы можно было подсвечивать новые. Этот экшен будет помечать все обновления прочитанными.

Есть два селектора - getNotifications и getUnreadNotificationsCount.

Thunk

Добавляем также thunk creator fetchNotifications, который будет обращаться к "серверу" и получать новые уведомления.

Из интересного - функция принимает два параметра - первый пользовательский, если в thunk нужно передать какие-то данные, второй - thunkAPI - объект с методами для работы с хранилищем (getStore, dispatch).

Нужно также добавить редьюсер для экшена fetchNotifications.fulfilled в секцию extraReducers. Раньше мы всегда оформляли ее как функцию и добавляли все обработчики через объект builder. Но тут используется другой синтаксис - объект, поля которого представляют собой имена экшенов. Так тоже можно.


extraReducers: {
[fetchNotifications.fulfilled]: function(state, action) {}
}


Выглядит более понятно, чем вариант с функцией, но тот обычно удобнее.

Список

Добавляем компонент NotificationsList, который будет рендерить список уведомлений, а также помечать все уведомления прочитанными. Для этого используем хук useLayoutEffect.

Кроме того, добавляем в App.js новый роут notifications, а в шапку - новую ссылку. Плюс в шапке еще появилась кнопка для обновления списка (получения новых уведомлений). Если ее нажать, появится количество непрочитанных сообщений, которое обнулится при переходе на страницу списка.

#redux #управлениесостоянием #документация #примерыкода
Redux Essentials 13. Мемоизация селекторов

В текущей версии проекта (https://qmk9tf.csb.app/users/1) перейдем на страницу пользователя, откроем в панели разработчика вкладку Profiler (из React DevTools) и нажмем на кнопку записи.

Теперь обновим список уведомлений (кнопка в шапке) и после этого остановим запись.

Увидим, что помимо шапки (в которой появилось кол-во непрочитанных сообщений), перерендерилась еще и страница UserPage. Казалось бы, с чего это вдруг, в этом компоненте изменений не было.

Впрочем, мы уже знаем, в чем дело - в селекторе getUserPosts, который возвращает новый массив, так как используем метод filter.

Чтобы решить проблему, используем метод createSelector (из библиотеки reselect, берем из пакета @reduxjs/toolkit).

У него будут два входных селектора - уже существующий getPosts, который возвращает массив всех статей, и еще один, который возвращает только id пользователя. Выходной селектор уже фильтрует полученный массив. Таким образом, если общий список не изменится, перерендера не будет.

Обновленный код проекта: https://codesandbox.io/s/redux-essentials-memoized-selectors-react-junior-e8u8e6?file=/src/features/posts/postsSlice.js

Сам проект: https://e8u8e6.csb.app/users/1

#redux #управлениесостоянием #документация #примерыкода
👍1
Redux Essentials 14. Мемоизация компонентов

Еще одна проблема производительности в текущей версии проекта (https://qmk9tf.csb.app/).

Если на главной странице (со списком постов) поставить реакцию под одной из статей, то перерендерятся ВСЕ статьи, а не только та, которая изменилась.

Дело в том, что при изменении одного объекта статьи меняется весь массив статей. Из-за этого перерендеривается весь компонент PostsList, а с ним и все его потомки.

Есть несколько способов решить эту проблему.

Первый - обернуть компонент PostExcerpt в React.memo. Тогда он не будет перерендериваться при перерендере родителя (если у него не поменяются пропсы).

Обновленный код проекта: https://codesandbox.io/s/redux-essentials-memoized-selectors-react-junior-e8u8e6?file=/src/features/posts/PostsList.js

Сам проект: https://e8u8e6.csb.app/

Второй - переписать селектор getPosts, чтобы он возвращал только айдишники статей, и мемоизировать его с помощью shallowEqual. А в компоненте PostExcerpt получать только id и по нему находить нужный пост.

Третий - переделать состояние в целом, чтобы в нем хранился массив айдишников, который изменяется только при изменении набора постов. Другими словами, нормализовать состояние. Этим займемся в следующий раз.

#redux #управлениесостоянием #документация #примерыкода
👍1
Плохие привычки Middle React разработчиков

Статья (англ.): https://dev.to/srmagura/bad-habits-of-mid-level-react-developers-b41

Автор статьи перечисляет частые ошибки и плохие практики, которые видит, делая ревью чужого кода. В комментариях согласны не со всем :)

Итак, React-разработчики часто:

👎 Дублируют состояние

Плохо выглядит и может привести к рассинхрону.

👎 Не используют редьюсеры

Для работы с более-менее сложным состоянием редьюсеры (useReducer, redux) предпочтительнее простого useState. В частности, автор рекомендует работать через редьюсер с любым массивом, который можно редактировать.

👎 Не пишут юнит-тесты

Речь в статье идет о тестах, написание которых не занимает много времени, но приносит большую пользу, например, о тестах для редьюсеров.

👎 Недостаточно используют React.memo, useMemo и useCallback

Что приводит к проблемам с производительностью.

👎 Неправильно используют useEffect

Эффект запускается либо слишком часто, либо недостаточно часто. Из полезного: автор рекомендует помещать функции, которые задействованы в эффекте в useRef, чтобы сохранять их идентичность.

👎 Забывают про юзабилити

Не связано напрямую с React и вообще много где встречается.

👎 Не стремятся к совершенствованию знаний в CSS и веб-дизайне

Добавка к предыдущему пункту.

В заключение автор дает несколько советов-"хороших практик":

👍 Использовать исключительно TypeScript

👍 Использовать библиотеку для запросов (data-fetching)

👍 Использовать серверный рендеринг, только если он вам действительно нужен

👍 Использовать модульный CSS и связывать стили с компонентами

#ссылки #паттерны
👍31