Зображення автора
Вітаю! Під час вивчення Redux у мене виникла ідея поступово занотовувати отримані з різних джерел знання для зручності подальшого використання. Тому ця стаття буде корисною для початківців у Frontend розробці, щоб налаштувати проект із використанням React, Redux Toolkit та TypeScript.
1. Встановіть шаблон React додатку з використанням TypeScript за допомогою create-react-app.
npx create-react-app my-app --template typescript
Де my-app
— це назва директорії, в яку буде встановлено проект. Якщо ви вже знаходитесь у потрібній директорії і не хочете створювати нову для проекту, використовуйте замість назви крапку (.). У створеному проекті залиште тільки необхідні вам файли, все зайве видаліть.
2. Додайте пакети Redux Toolkit та React Redux до проекту.
npm install @reduxjs/toolkit react-redux
3. Створіть Redux сховище (store).
Створіть файл src/store/store.ts
. Імпортуйте API configureStore
з Redux Toolkit. Спочатку створіть порожнє Redux-сховище та експортуйте його:
import { configureStore } from '@reduxjs/toolkit'
export default configureStore({
reducer: {},
})
Redux Toolkit забезпечує простоту використання, тому при використанні configureStore
вам не потрібно вручну налаштовувати додаткові типи для стану чи диспетчера. Але важливо розуміти, як правильно отримувати та використовувати типи RootState
і AppDispatch
.
RootState
— це тип, який представляє весь глобальний стан вашого додатка. Використовуйте його, щоб гарантувати правильну типізацію у всіх селекторах і уникнути помилок.
AppDispatch
— це тип, що враховує всі middleware (наприклад, thunk) у вашому Redux-сховищі. Завдяки цьому ваш dispatch
знатиме, як обробляти звичайні об'єкти та асинхронні функції (thunks*).
**Thunks* — це спеціальні функції, які дозволяють виконувати асинхронні операції (наприклад, запити до API) перед тим, як оновити глобальний стан через
dispatch
.
Основна перевага використання типів RootState та AppDispatch.
Коли ви оновлюєте структуру сховища, configureStore
автоматично оновлює ці типи. Це означає, що вам не потрібно вручну вносити зміни — типи завжди будуть актуальними.
Додайте експорт типів RootState
і AppDispatch
до файлу src/store/store.ts
для подальшого використання і забезпечення типізації.
import { configureStore } from '@reduxjs/toolkit';
export const store = configureStore({
reducer: {},
});
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
4. Використайте типізовані хуки.
У React Redux часто потрібно працювати з хуками useDispatch
і useSelector
. Щоб зробити цей процес простішим і уникнути зайвого дублювання коду, створіть типізовані версії цих хуків у файлі src/store/hooks.ts
.
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
За замовчуванням useDispatch
працює з типом Dispatch
, який не підтримує thunks (асинхронні функції). Щоб правильно відправляти thunks через dispatch
, потрібно використовувати спеціальний тип AppDispatch
, який враховує thunk middleware вашого Redux-сховища. Створіть типізований хук для useDispatch
, щоб не було потреби вручну імпортувати AppDispatch
у кожному компоненті.
Прим. У цій публікації не буде використано можливості thunks.
З ними ви можете ознайомитися на прикладі отримання даних користувачів з API у наступній публікації.
Замість того, щоб кожного разу в useSelector
вказувати тип стану ((state: RootState)
), створіть хук useAppSelector
, який автоматично враховує тип вашого глобального стану RootState
.
5. Надайте доступ до сховища (store) для всіх компонентів React.
Для цього використовуйте React Redux Provider
у файлі src/index.tsx
. Зробіть імпорт створеного в п.3 сховища (store) та обгорніть ваш додаток у <Provider>
, якому в пропс передайте сховище (store):
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import { store } from "./store/store";
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<Provider store={store}>
<App />
</Provider>
);
6. Створіть Redux Slice — частину глобального стану (store), разом з логікою, яка керує цією частиною. Redux Toolkit спрощує створення slice, об’єднуючи все необхідне в одному місці: початковий стан, ред’юсери (reducers) та екшени (actions).
Якщо, наприклад, ви працюєте зі списком користувачів, то ваш slice розмістіть у файлі src/store/usersSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import users from './users';
// Визначення типу користувача
export interface User {
id: number;
name: string;
username: string;
email: string;
phone: string;
// будь-які інші поля
}
// Визначення типу стану Slice
interface UsersState {
users: User[];
}
// Початковий стан
const initialState: UsersState = {
// поки ви не отримуєте дані з api, створіть в окремому файлі users.ts
// масив об'єктів users (поля об'єктів мають співпадати з інтерфейсом User),
// та екпортуйте його для викоритання у початковому стані
users: users,
};
// Створення slice
const usersSlice = createSlice({
name: 'users', // Унікальне ім'я
initialState, // Початковий стан
reducers: { // Ред'юсери
// Додаємо нового користувача
addUser(state, action: PayloadAction<User>) {
state.users.push(action.payload);
},
// Видаляємо користувача
removeUser(state, action: PayloadAction<number>) {
state.users = state.users.filter(user => user.id !== action.payload);
},
// будь-які інші ред'юсери, пов'язані з діями над користувачами
},
});
// Експорт екшенів (actions) для використання у компонентах
export const { addUser, removeUser } = usersSlice.actions;
// Експорт ред'юсера
export default usersSlice.reducer;
Створіть usersSlice за допомогою createSlice, яку імпортуйте з Redux, та передайте їй об’єкт з наступними властивостями:
- name — унікальне ім’я Slice
- initialState — початковий стан Slice
- reducers — синхронні ред’юсери для виконання дій (action) над станом (додавання, видалення, тощо)
Action — це об’єкт із двома обов’язковими властивостями:
{
type: string; // Ідентифікатор дії
payload?: any; // Дані, передані разом із дією (необов'язково)
}
type
визначає, яку саме дію потрібно виконати (наприклад, видалення користувача).payload
містить додаткові дані (у випадку видалення —id
користувача, якого потрібно видалити).
Коли Redux отримує екшен (action), він викликає відповідний ред’юсер на основі значення type
. Дані доступні у цьому ред’юсері через параметр action.payload
. У TypeScript для типізації можна використовувати PayloadAction<>
із вказанням типу даних.
Як бачите, ви можете змінювати стан прямо в ред’юсерах state.users.push(newUser)
. Це стає можливим завдяки тому, що Redux Toolkit використовує бібліотеку Immer, яка дозволяє писати “мутабельну” логіку оновлення стану.
Це спрощує код, оскільки не потрібно вручну створювати новий об’єкт стану.
7. Інтегруйте Slice у Redux Store.
Поверніться до файлу сховища (store), який ви створили в п.3 src/store/store.ts
, та додайте Slice до глобального стану.
import { configureStore } from '@reduxjs/toolkit';
import usersReducer from './usersSlice';
export const store = configureStore({
reducer: {
users: usersReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
8. Використайте Slice у компонентах.
Тепер все готово для використання сховища (store) у ваших React-компонентах.
Розглянемо використання Slice на прикладі компонента UsersList, в якому виводиться список користувачів, та кожен користувач має кнопку “Видалити” для видалення даних. Розташуємо компонент за адресою src/components/UsersList/UsersList.tsx
.
import React, { useEffect } from 'react';
import { useAppDispatch, useAppSelector } from '../store/hooks';
import { removeUser } from '../store/usersSlice';
const UsersList: React.FC = () => {
const dispatch = useAppDispatch();
const users = useAppSelector((state) => state.users);
// Видалення користувача за id
const handleRemoveUser = (id: number) => {
dispatch(removeUser(id));
};
return (
<div>
<h2>Список користувачів</h2>
{users.map(user => (
<div key={user.id}>
{user.name} ({user.username}) - {user.email} - {user.phone}
<button onClick={() => handleRemoveUser(user.id)}>Видалити</button>
</div>
))}
</div>
);
};
export default UsersList;
При натисканні на кнопку “Видалити” викликається обробник події (Event Listener) handleRemoveUser
, в яку передається user.id
. Далі функція dispatch
передає екшен (action) removeUser
до Redux Store і запускає відповідний ред’юсер, який модифікує стан. Ред’юсер removeUser
виконується та у стан записується новий масив без видаленого користувача.
Налаштування проєкту з React, Redux Toolkit та TypeScript може здатися складним на перший погляд, але воно дає величезну перевагу для роботи зі станом. Це як побудувати міцний фундамент для свого будинку: спочатку потрібно попрацювати, але потім усе стає набагато простіше. Redux Toolkit усуває необхідність ручного налаштування Redux, а TypeScript допомагає уникнути помилок ще на етапі розробки та робить код більш зрозумілим. Завдяки цьому ви швидше зможете створювати надійні додатки, які легко розвивати та підтримувати.
Ця комбінація технологій дозволить вам зосередитися на головному — написанні якісного коду та розвитку ваших навичок. Тож не бійтеся зануритися у процес налаштування. Це невелика інвестиція, яка дуже швидко себе виправдає!
Перекладено з: Налаштування проєкту React, Redux Toolkit, TypeScript