Redux - Це інструмент для управління станом програми. Побудований на принципах технології Flux та функціонального програмування. Створений компанією FaceBook, але всупереч поширеній думці може використовуватися не тільки у зв'язці з React, але також з іншими фреймворками/бібліотеками. Flux – це тип архітектури та набір патернів проектування веб-додатків. Детальніше на Wiki Redux використовує методологію flux. Вона складається з 4 понять: У React за замовчуванням немає якогось глобального state (стану), який був би доступний у всьому додатку. Ви можете лише зберігати дані в межах одного компонента. Наприклад, у вас є інтернет-магазин і в ньому є кошик з товарами. Якщо працювати тільки зі стейтом компонента Кошик, то ці дані будуть недоступні в інших компонентах. Також, наприклад, у вас є іконка кошика в кутку екрана, яка повинна показувати кількість товару, які користувач додав туди. Так от засобами чисто React, це буде складно реалізувати. Саме тому є такі бібліотеки як Redux, для зберігання всіх даних програми в одному місці та зручного їх оновлення. Як я вже писав вище, основні поняття редаксу actions, dispatcher, store. Store - Це стан веб-компонента, який зберігає в собі всю інформацію (або ту, яку ви вирішили зберегти в нього). Надалі сторінка буде доступна з будь-якого компонента вашої програми. Action - Дія, що визначає що потрібно зробити. Відповідно до принципів функціонального програмування, ми не можемо змінювати об'єкт безпосередньо, тому нам потрібні екшени, щоб передати їх у диспатчер і «сказати», що потрібно зробити. Dispatcher
— повідомляє сховищу про якусь дію (action) та передає йому оновлену інформацію. Тепер коли ми розібрали основні поняття, давайте подивимося, як працює Redux: Компонент генерує дію (action), диспатчер повідомляє про це сховище (store), сховище змінює стан і дані передаються компонент (View). Є ще одне поняття у Redux – це reducer (редюсер). Редюсер – це чиста функція, яка приймає як аргумент сховище та екшен. Основні правила редюсерів: Докладніше про чисті функції можна прочитати тут. Тепер на простому практичному прикладі розберемо, як працювати з Redux. Ми зробимо простий додаток ToDo, який дасть можливість створювати свої таски зі збереженням їх у store. Це буде простий додаток для прикладу, основний наголос зроблений на роботу з Redux. Отже, є два варіанти, ви можете завантажити стартовий проект і просто запустити установку, або покроково пройти і створити проект зі старту. Щоб створити програму заново, відкриваємо командний рядок або Git Bash Далі заходимо в папку проекту та встановлюємо Redux та пакет для Реакту – react-redux Якщо ви завантажили архів із вже готовим додатком, тоді його потрібно розпакувати, увійти в папку з додатком та в командному рядку/терміналі запустити команду: Тепер, щоб запустити наш проект, потрібно скористатися наступною командою: Проект буде зібраний та запущений, автоматично відкриється вкладка у браузері Результат виконання команди npm run start Для того, щоб не верстати все знову, ми використовуємо Bootsrap. Давайте його встановимо. Також підключимо стилі у файлі src/index.js Тепер зробимо базову структуру для Redux. Створимо папку src/store, а в ній 4 файли Спочатку визначимо, які типи екшенів нам потрібні у файлі actionTypes.js. Можна типи і не визначати, але надалі це дасть нам змогу скоротити час на дебагінг, якщо раптом знадобиться змінити ім'я екшену, всі вони знаходяться в одному місці, що теж зручно. Якщо нам знадобиться екшен в іншому місці, нам буде достатньо імпортувати його в іншому модулі. У нашому додатку, наприклад, нам потрібен буде екшен у 2 файлах: actions та reducer. Створимо файл actionTypes.js і в ньому визначимо наші типи: У файлі store/actions.js ми опишемо всі екшени, які нам потрібні для застосування: Вище ви бачите типову структуру екшену: це функція, яка повертає об'єкт із двома властивостями: Тепер розглянемо функцію редюсер (store/reducer.js): Тут ми імпортуємо наші типи екшенів, потім визначаємо змінну для того, щоб задавати ID кожному новому таску. Сам reducer приймає як аргументи state (або дорівнює порожньому масиву) і екшен. Далі ми перевіряємо тип екшену і залежно від цього робимо певні маніпуляції зі стейтом. Давайте розмеберемо з прикладу екшену TASK_ADD. При додаванні нового таска, нам необхідно зробити копію поточного стейту та додати до нього новий таск Далі на підставі цього редюсера нам потрібно створити store за допомогою функції createStore. Створимо файл store.js з таким вмістом: * У мене також другим параметром додано слід. рядок Це для роботи плагіна для Chrome – Redux DevTools. Зручний плагін для дебагінгу. Тепер після створення store, ми можемо використовувати його у будь-якому модулі нашої програми. Основні методи роботи зі store Наш додаток буде побудовано за допомогою функціональних компонентів та хуків, тому ми трохи розглянемо, які хуки надає нам Redux для роботи в таких компонентах. Тепер давайте повернемося до нашої програми. В index.js нам потрібно обернути нашу програму в компонент Provider і передати йому store через пропси. Store ми створили у файлі store/store.js Наступним кроком давайте створимо папку, в якій зберігатимемо наші компоненти і назвемо її componetns. У ній створимо 3 файли: Спочатку імпортуємо хук useDispatch(), т.к.у цьому компоненті ми будемо диспатчити 2 екшени: виконання таска (toggleTask) та видалення (removeTask). У компоненті у нас є подія onChange - при кліку на цей компонент ми будемо диспатчити екшен для перемикання стану таска, ну а при кліку кнопки видалити - диспатчім екшен для видалення таска з нашого стору. Далі файл src/components/TaskList.js Тепер останній компонент AddNewTask.js Тут у нас буде 2 обробники handleTaskTitleChange() та handleTaskSubmit(). Останній диспатчитиме екшен для створення нового тяга. В ідеалі, тут ще додати перевірку на порожній рядок і обрізати зайві прогалини, але у нас не про це зараз 🙂 Тепер залишився заключний крок, змінити файл scr/App.js та додати трохи стилів У результаті у нас вийшов такий додаток У контексті сучасної розробки, де складність веб-додатків тільки зростає, Redux виступає як надійний партнер, полегшуючи керування даними, забезпечуючи передбачуваність та покращуючи тестованість коду. У наступних розділах ми більш докладно розглянемо, що таке redux, що він є і чому він вартий вашої уваги. Redux - це JavaScript-бібліотека, покликана спростити керування станом вашої веб-програми. Її основне призначення полягає в тому, щоб зробити керування даними більш організованим та передбачуваним. У центрі концепції Redux знаходиться сховище стану (Store). Це своєрідне сховище, де зібрані всі дані вашої програми. Однак важлива відмінність від традиційного підходу полягає в тому, що Redux пропонує єдине централізоване сховище, доступне для всіх компонентів вашої програми. Це як загальний банк даних, якого можна звернутися з будь-якої точки докладання. Отже, цей інструмент дозволяє нам ефективно управляти даними, роблячи процес розробки організованішим і передбачуваним. Принцип того, як працює redux, ґрунтується на таких концепціях: Чому використання Redux - це саме те, що може зробити ваше життя розробника простіше і більш передбачуваним? Перша важлива причина – це спрощення керування станом вашої програми. Ви, напевно, стикалися з ситуаціями, коли дані повинні бути доступні в різних частинах програми. Замість того, щоб передавати ці дані через безліч компонентів, Redux надає централізоване сховище, яке можна використовувати з будь-якої точки вашої програми. Це особливо цінно, коли вам потрібно обмінюватись даними між різними компонентами. Уявіть це як загальний ресурс, до якого ви маєте доступ у будь-який момент. Але це ще все. Він приносить нам передбачуваність та тестованість нашого коду. Тут справа в тому, що всі зміни стану відбуваються через Actions та Reducers. Нічого не відбувається раптово та несподівано. Ми точно знаємо, які дії викликаються та які редьюсери обробляють ці зміни. Це робить процес налагодження та тестування набагато більш простим та передбачуваним. Redux варто використовувати, коли: Однак варто пам'ятати, що Redux не підходить для всіх програм. У деяких випадках може додати надмірну складність. Інструмент має ряд переваг, але також деякі недоліки: Переваги: Недоліки: Важливо оцінити потреби вашого проекту: для невеликих і простих програм цей інструмент може здатися зайвим, але у разі складних додатків з безліччю компонентів і складним глобальним станом він може значно покращити організацію коду та забезпечити легкість його супроводу. Redux спирається на три ключові компоненти для керування станом програми: Store, Actions та Reducers. Давайте розглянемо кожен із них докладніше. Store (сховище) є центральним компонентом Redux. Це об'єкт, який містить глобальний стан вашої програми. Роль Store полягає в тому, щоб зберігати та надавати доступ до даних, які вашому додатку необхідні. Store є деревоподібною структурою даних, де кожна частина стану має свій шлях (схожий на шлях до файлу у файловій системі). Це означає, що доступ до даних здійснюється через ключі і кожен ключ вказує на конкретну частину стану. Actions є об'єктами, які описують події або сигнали. Вони повідомляють, що щось сталося у вашому додатку і служать для ініціювання змін стану Redux. ⚡ Готові поринути у світ Front End розробки? 🌐 З нашим онлайн курсом Front End навчайтеся будь-де і будь-коли! 🚀 Переконайтеся, що Front End є вашим покликанням протягом 7 днів тестового періоду. 👨🏫 Професійні ментори завжди готові допомогти вам. Сьогодні розглянемо бібліотеку Redux для JS, навіщо вона потрібна, і чи варто вона вашої уваги. Redux – це бібліотека для керування станом програми. Redux створений для тих випадків, коли: Головний принцип Redux - одне джерело правди. Всі дані програми зберігаються в одному місці - в store. Якщо хочеться щось змінити: Найчастіше Redux використовується у зв'язці з React, і це не дивно – react-redux робить їхню спільну роботу неймовірно зручною. Але при цьому Redux цілком може працювати з іншими фреймворками (або навіть без них). Для початку встановимо Redux та його дружка - react-redux : redux – це ядро бібліотеки. React-redux — це набір інструментів для інтеграції Redux з React. Почнемо з головного - store. Це центральне сховище стану. Все, що ви зберігатимете, знаходиться тут: Редьюсер отримує поточний стан та дію (action) і повертає новий стан. Action – це просто об'єкт із обов'язковим полем type . Приклад: Кожна дія каже редьюсерові: «Давай щось зробимо». Ось редьюсер із нашого прикладу: Настав час об'єднати Redux із React. Це простіше, ніж здається завдяки react-redux : Provider робить store доступним для всіх компонентів: Тепер підключаємо хуки useSelector і useDispatch і будь-який компонент зможе отримувати дані зі store через useSelector і відправляти дії через useDispatch : Ванільний Redux вимагає багато шаблонного коду: окремі екшени, ред'юсери, константи. Все це звучить трохи стомлюючим. Ось чому з'явився Redux Toolkit. Це набір інструментів, який значно спрощує роботу і саме їм користуються при роботі з Redux на сьогоднішній день. З Redux Toolkit можна: Тепер замість того, щоб писати тонни коду для екшенів та ред'юсерів, все це створюється автоматично. Докладніше про Toolkit можна глянути тут. Одна з найпотужніших, але недооцінених на мій погляд властивостей Redux – це middleware. По суті, це функції, що сидять між екшенами та ред'юсерами, і можуть перехоплювати дії, додавати додаткову логіку або навіть модифікувати екшени під час їхньої дії. У Redux middleware застосовуються через функцію applyMiddleware: redux-logger виводить інформацію про кожен екшен та стан у консоль. Redux по дефолту синхронний, але для роботи з асинхронними операціями, наприклад, запитами до API, є спеціальне middleware redux-thunk : А тепер створимо асинхронний екшен: Тепер Redux може опрацьовувати складні асинхронні сценарії. Тепер перейдемо до практики. Уявимо, що є інтернет-магазин, що спеціалізується на товарах для котиків: іграшки, корм, лежанки, лазилки і все, що може зробити щасливим пухнастого клієнта. Нам потрібно керувати такими станами програми: Щоб зробити програму масштабованою та підтримуваною, розіб'ємо стан на кілька модулів: products, cart, auth, order. Кожен модуль представлятиме свою «частину стану» (state slice) і працюватиме через Redux Toolkit. Спочатку налаштуємо модуль для роботи з товарами. Допустимо, ми завантажуємо список товарів із сервера. Slice для товарів: Тепер є асинхронний екшен fetchProducts, який завантажує товари та оновлює стан. У кошику потрібно вміти додавати товари, змінювати їх кількість та видаляти. Також зберігатимемо загальну кількість товарів та суму замовлення. Slice для кошика: Авторизація потрібна для оформлення замовлення та збереження історії покупок. Будемо зберігати інформацію про поточного користувача в модулі auth. Slice для авторизації: Після вибору товарів та авторизації відправляємо дані замовлення на сервер. Slice для замовлення: І ось так, крок за кроком ми побудували інтернет-магазин для котиків, який може впоратися з будь-яким капризом пухнастих клієнтів: від кошика до оформлення замовлення. Redux став сполучною ланкою між усіма частинами нашого додатка – управління товарами, авторизація, обробка замовлень – все чітко та передбачувано. Завдяки Redux Toolkit обійшлися без тонни шаблонного коду. Redux – штука потужна, але не панацея. Він ідеальний, якщо у вас складна програма з купою станів, якими потрібно керувати централізовано. Далі річ за вами: експериментуйте, пробуйте різні підходи, але не забувайте, що Redux потрібний не завжди. Іноді і Context API за очі вистачить. Якщо хочеться копнути глибше: Що ще вивчити? Розберіться з redux-thunk та redux-saga для асинхронщини, гляньте Reselect для оптимізації селекторів. І обов'язково пограйтеся з Redux DevTools. Діліться своїм досвідом роботи з Redux та порадами для початківців!Основи Redux для початківців
Основні поняття Redux
Простий приклад використання Redux
Встановлення та налаштування проекту
npx create-react-app redux-first-app
cd redux-first-app/ // Потім npm i redux react-redux
npm install react-bootstrap bootstrap
import React from 'react'; import ReactDOM від 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals від './reportWebVitals'; import 'bootstrap/dist/css/bootstrap.min.css'; ReactDOM.render(
Створення базової структури для сховища
export const TASK_ADD = 'TASK_ADD'; export const TASK_TOGGLE = 'TASK_TOGGLE'; export const TASK_REMOVE = 'TASK_REMOVE';
import * as actions from './actionTypes'; export const addTask = task => (<type: actions.TASK_ADD, payload: task>); export const toggleTask = (<type: actions.TASK_TOGGLE, payload:<id>>); export const removeTask = (< type: actions.TASK_REMOVE, payload: < id >>)
import * as actions from './actionTypes'; let lastId = 0; export default function reducer(state = [], action) < switch (action.type) < case actions.TASK_ADD: return [.state, < id: ++lastId, title: action.payload.title, completed: false, >]; case actions.TASK_TOGGLE: return state.map(task => < if (task.id === action.payload.id) return < . task, completed: !task.completed >return task; >); case actions.TASK_REMOVE: return state.filter(task => action.payload.id !== task.id); default: return state; > >
import < createStore > від 'redux'; import reducer from "./reducer"; const store = createStore( reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); export default store;
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
Redux у функціональних компонентах (хукі)
import React from 'react'; import ReactDOM від 'react-dom'; import < Provider >from 'react-redux'; import './index.css'; import App from './App'; import reportWebVitals від './reportWebVitals'; import 'bootstrap/dist/css/bootstrap.min.css'; import store from './store/store'; ReactDOM.render(
import React від "react"; import < useDispatch >з "react-redux"; import < Form, ListGroup >з "react-bootstrap"; import < toggleTask, removeTask >з "../store/actions"; const Task = (< task >) => < const < id, title, completed >= task; const dispatch = useDispatch(); // Отримуємо диспатч із хука return (
import React from 'react'; import < ListGroup >з "react-bootstrap"; import Task from './Task'; // імпортуємо компонент таска, створений раніше const TaskList = (< tasks >) => < if (tasks.length) return (
import React, < useState > from 'react'; import < Button, FormControl, InputGroup >з "react-bootstrap"; import * as actions from "../store/actions"; import < useDispatch >з "react-redux"; const AddNewTask = () => < const [taskTitle, setTaskTitle] = useState(''); const dispatch = useDispatch(); const handleTaskTitleChange = (e) => < setTaskTitle(e.target.value); >const handleTaskSubmit = () => < dispatch(actions.addTask(< title: taskTitle >)); setTaskTitle(''); > return (
import React from 'react'; import < Container, Col, Row >з "react-bootstrap"; import TaskList from "./components/TaskList"; import AddNewTask from "./components/AddNewTask"; import < useSelector >from "react-redux"; import './App.css'; function App() < // Отримуємо наш state const tasks = useSelector(state =>state); return (
.main-app-container .list-group-item < display: flex; >.list-group-item .form-check < width: 90%; >.list-group-item-actions < width: 10%; text-align: right; >.list-group-item-actions span .main-app-container .list-group-item:hover .list-group-item-actions span < display: inline; cursor: pointer; >.list-group-item-actions span:hover < text-decoration: underline; >.list-group-item.task-completed < background-color: #f2f2f2; >.list-group-item.task-completed .form-check-label
Список завдань
Ваші запитання та коментарі:
Свіжі записи
Для чого та коли використовується Redux
Що таке?
Чому варто використати?
Коли потрібно його використовувати?
Основні компоненти
Redux для новачків: база, з якої можна стартувати
Основний функціонал Redux
npm install redux react-reduxСтворення Store
import < createStore > від 'redux'; // Початковий стан const initialState = < counter: 0, >; // Ред'юсер - чиста функція, яка оновлює стан function counterReducer(state = initialState, action) < switch (action.type) < case 'INCREMENT': return < . state, counter: state.counter + 1>; case 'DECREMENT': return < . state, counter: state.counter - 1>; default: return state; > > // Створюємо store const store = createStore(counterReducer); console.log(store.getState()); //
Actions: кажемо, що робити
const incrementAction = <type: 'INCREMENT'>; const decrementAction = <type: 'DECREMENT' >;Reducer
function counterReducer(state = initialState, action) < switch (action.type) < case 'INCREMENT': return < . state, counter: state.counter + 1>; case 'DECREMENT': return < . state, counter: state.counter - 1>; default: return state; > >
Підключення React та Redux
import React from 'react'; import ReactDOM від 'react-dom'; import < Provider >from 'react-redux'; import < createStore > від 'redux'; import counterReducer from './reducers'; const store = createStore(counterReducer); ReactDOM.render( import React from 'react'; import < useSelector, useDispatch >from 'react-redux'; function Counter() < const counter = useSelector((state) =>state.counter); const dispatch = useDispatch(); return ( Redux Toolkit
import < configureStore, createSlice >from '@reduxjs/toolkit'; // Створюємо slice (ред'юсер + екшени) : (state) => < state.value -= 1; > incrementByAmount: (state, action) => < state.value += action.payload; // Екшен export const < increment, decrement, incrementByAmount >= counterSlice.actions; // Store const store = configureStore(< reducer: < counter: counterSlice.reducer, >, >); export default store;Middleware
import < createStore, applyMiddleware >from 'redux'; import logger from 'redux-logger'; const store = createStore(rootReducer, applyMiddleware(logger));Асинхронність та Thunk
npm install redux-thunkimport < createStore, applyMiddleware >from 'redux'; import thunk from 'redux-thunk'; const store = createStore(rootReducer, applyMiddleware(thunk));// actions/userActions.js export const fetchUsers = () => async (dispatch) => < dispatch(< type: 'FETCH_USERS_REQUEST' >); try < const response = await fetch('https://jsonplaceholder.typicode.com/users'); const data = await response.json(); dispatch(< type: 'FETCH_USERS_SUCCESS', payload: data >); > catch (error) < dispatch(< type: 'FETCH_USERS_FAILURE', payload: error.message >); > >;Приклад застосування бібліотеки
Підготовка стану товарів
import < createSlice, createAsyncThunk >from '@reduxjs/toolkit'; // Асинхронний thunk для завантаження товарів export const fetchProducts = createAsyncThunk('products/fetchProducts', async() => < const response = await fetch('https://api.example.com/cat-products'); const data = await response.json(); const productsSlice = createSlice(< name: 'products', initialState: < items: [], status: 'idle', // idle | loading | succeeded | failed error: null, >, reducers: <>, extraReducers: (builder ) => < Builder. ; state.items = action.payload; > .addCase(fetchProducts.rejected, (state, action) => < state.status = 'failed'; state.error = action.error.message; >); ); export default productsSlice.reducer;Управління кошиком
import < createSlice >from '@reduxjs/toolkit'; const cartSlice = createSlice(< name: 'cart', initialState: < items: [], // [< id, name, price, quantity >] totalItems: 0, totalPrice: 0, >, reducers: < addToCart: (state , action) => < const product = action.payload; const existingItem = state.items.find((item) =>item.id === product.id); >else < state.items.push(< . product, quantity: 1 >); > state.totalItems += 1; state.totalPrice += product.price; = action.payload; const existingItem = state.items.find((item) =>item.id === productId); < state.totalItems -= existingItem.quantity; state.totalPrice -= existingItem.price * existingItem.quantity; updateQuantity: (state, action) => < const < id, quantity >= action.payload; const existingItem = state.items.find((item) => item.id === id); quantityDiff = quantity - existingItem.quantity; existingItem.quantity = quantity; state.totalItems += quantityDiff; state.totalPrice += quantityDiff *existingItem.price; >>, >, >); export const < addToCart, removeFromCart, updateQuantity >= cartSlice.actions; export default cartSlice.reducer;Авторизація користувача
import < createSlice, createAsyncThunk >from '@reduxjs/toolkit'; export const login = createAsyncThunk( 'auth/login', async (credentials) => < const response = await fetch('https://api.example.com/login', < method: 'POST', body: JSON). stringify(credentials), headers: < 'Content-Type': 'application/json' >, >), const data = await response.json(); const authSlice = createSlice(< name: 'auth', initialState: < user: null, token: null, status: 'idle', error: null, >, reducers: < logout: (state) =>< state.user = null; state.token = null; >, >, extraReducers: (builder) => < builder. , (state, action) => < state.status = 'succeeded'; ) => < state.status = 'failed'; export const < logout >= authSlice.actions; export default authSlice.reducer;Оформлення замовлення
import < createSlice, createAsyncThunk >from '@reduxjs/toolkit'; export const submitOrder = createAsyncThunk( 'order/submitOrder', async (orderDetails, < getState >) => < const < token >= getState().auth; const response = await fetch('https://api.example.com /orders', < method: 'POST', body: JSON.stringify(orderDetails), headers: < 'Content-Type': 'application/json', Authorization: `Bearer $`, >, >); .json(); > ); const orderSlice = createSlice(< name: 'order', initialState: < status: 'idle', error: null >, reducers: <>, extraReducers: (builder) => < builder .addCase(submitOrder.pending, (state) =>< state.status = 'loading'; >) .addCase(submitOrder.fulfilled, (state) => < state.status = 'succeeded'; >) .addCase(submitOrder.rejected, (state, action) => < state.status = 'failed'; export default orderSlice.reducer;Що в результаті
dispatch(removeTask(id))>>Видалити // Диспатчі екшен removeTask для видалення тяга зі стору
) > export default Task;Counter:
); > export default Counter;