Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Last active October 28, 2025 21:33
Show Gist options
  • Save sunmeat/8503dc901e4c65036fa5b41658ce10a0 to your computer and use it in GitHub Desktop.
Save sunmeat/8503dc901e4c65036fa5b41658ce10a0 to your computer and use it in GitHub Desktop.
simple redux example
App.jsx:
// імпортуємо хуки та компонент Provider з react-redux
import {useSelector, useDispatch, Provider} from 'react-redux'
// !!! npm install react-redux @reduxjs/toolkit !!!
// хук useSelector дозволяє отримати доступ до стану сховища - єдиного центру даних для всього додатка
// у сховищі (store) зазвичай лежить один великий об'єкт - дерево стану, для всього
// useSelector "селектить" (вибирає) потрібний шматок даних із цього глобального стану
// і підписує компонент на оновлення вибраної частини стану
// компонент буде перемальовано, коли вибраний стан зміниться
// хук useDispatch повертає функцію dispatch, за допомогою якої можна відправляти дії (actions) у сховище
// через dispatch запускаються зміни стану, описані в редьюсерах
// вважається, що це основний спосіб взаємодії компонентів із Redux-станом
// провайдер - це компонент, що обгортає весь додаток
// він робить Redux store доступним для всіх вкладених компонентів через контекст
// без нього хуки useSelector і useDispatch працювати не будуть, а компоненти не зможуть читати або змінювати глобальний стан
// це обов'язковий міст між Redux і React
// імпортуємо функції для створення слайса і сховища з redux toolkit
import {configureStore, createSlice} from '@reduxjs/toolkit'
// configureStore - це функція з Redux Toolkit, яка спрощує створення Redux store
// це більш сучасна і зручна заміна застарілому в серпні 2022 року createStore
// вона автоматично налаштовує devtools, middleware та інтеграцію з thunk
// devtools - інструмент для відстеження дій і станів у браузері
// middleware - функції-перехоплювачі, які обробляють дії між dispatch і reducer
// thunk - це різновид middleware, корисний для написання асинхронних дій (наприклад, API-запитів)
// взагалі, thunk ("глухий звук") - у програмістів означає шматок коду, який виконує певну відкладену роботу https://daveceddia.com/what-is-a-thunk/
// слайс (slice) - це "шматок" глобального стану та логіки, що до нього відноситься
// він включає початковий стан, редюсери та згенеровані дії
// кожен слайс відповідає за свою ізольовану частину бізнес-логіки (наприклад, лічильник, користувач, кошик тощо)
// функція createSlice допомагає описати частину стану (слайс), редюсери та дії одночасно
// вона створює і дії, і редьюсери автоматично, що скорочує обсяг коду
// це основний спосіб роботи з Redux Toolkit
import './App.css'
// створюємо слайс (шматок стану) з ім'ям 'counter'
// createSlice створює об'єкт з двома важливими властивостями: actions і reducer
const counterSlice = createSlice({
name: 'counter', // ім'я слайса, використовується для генерації типів дій
initialState: {count: 0}, // початковий стан лічильника
reducers: {
// функція-редьюсер для дії increment
increment: (state) => {
state.count += 1 // збільшуємо значення лічильника
},
decrement: (state) => {
state.count -= 1 // зменшуємо лічильник на 1
},
},
})
// витягуємо дію increment із створеного слайса
const {increment, decrement} = counterSlice.actions
// навіщо витягувати? основна причина - чистота коду і читабельність
// написати далі по коду dispatch(increment()) буде простіше, ніж dispatch(counterSlice.actions.increment())
// і одразу зрозуміло, що викликається дія increment
// створюємо сховище Redux з редьюсером із слайса
const store = configureStore({
reducer: counterSlice.reducer, // підключаємо редюсер до сховища
})
// createSlice упаковує окремі обробники дій із reducers в одну функцію counterSlice.reducer
// якщо кілька слайсів, то буде reducer: {
// counter: counterSlice.reducer,
// anotherSlice: anotherSlice.reducer,
// }
// компонент, у якому використовується стан і дії Redux
function Counter() {
const count = useSelector((state) => state.count) // отримуємо значення лічильника із сховища
const dispatch = useDispatch() // отримуємо функцію для відправки дій
return (
<>
<h1>Redux Toolkit</h1>
<div className="card">
<button onClick={() => dispatch(decrement())}>
зменшити
</button>
<button onClick={() => dispatch(increment())}>
збільшити
</button>
<p>значення лічильника {count}</p>
</div>
</>
)
}
// тут обов'язково обгортаємо Counter у Provider
function App() {
return (
<Provider store={store}> {/* передаємо сховище всім дочірнім компонентам */}
<Counter/>
</Provider>
)
}
// redux - це централізоване сховище стану додатка,
// і щоб компоненти могли отримати доступ до цього стану або змінити його, їм потрібно знати, де цей store
// напряму прокидати store через пропси у всі компоненти - це складно і марнування часу,
// особливо якщо компонентів багато і вони вкладені глибоко один в одного
// тому React Redux використовує контекст (React Context API), щоб "прокинути" store на самий верх
// і дати можливість будь-якому вкладеному компоненту отримати до нього доступ незалежно від рівня вкладеності
// провайдер приймає store у пропсі і через контекст робить це сховище доступним для всіх нащадків
// жоден компонент не отримує store напряму через пропси - натомість вони "підписуються" на контекст
// і за допомогою хуків useSelector і useDispatch працюють із сховищем
// хто "чекає" цей store? - усі компоненти, які використовують:
// useSelector - щоб вибрати частину стану зі store
// useDispatch - щоб відправляти дії (actions) у store
// під капотом ці хуки звертаються до React Context, створеного <Provider>, щоб отримати екземпляр store
// без <Provider> хуки просто не зможуть знайти і використовувати сховище, і додаток зламається
export default App
==========================================================================================================
App.css:
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea, #764ba2);
color: #f0f0f0;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
h1 {
text-align: center;
margin-bottom: 1rem;
font-weight: 700;
letter-spacing: 2px;
text-shadow: 0 0 8px rgba(255, 255, 255, 0.7);
}
.card {
background: rgba(255, 255, 255, 0.1);
padding: 2rem 3rem;
border-radius: 15px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
min-width: 320px;
}
button {
background: #764ba2;
color: #fff;
border: none;
padding: 0.6rem 1.5rem;
font-size: 1.1rem;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
transition: background 0.3s ease, transform 0.15s ease;
box-shadow: 0 4px 12px rgba(118, 75, 162, 0.5);
user-select: none;
}
button:hover {
background: #5e3880;
transform: scale(1.05);
box-shadow: 0 6px 18px rgba(94, 56, 128, 0.7);
}
button:active {
transform: scale(0.95);
box-shadow: 0 3px 10px rgba(94, 56, 128, 0.6);
}
p {
font-size: 1.3rem;
font-weight: 700;
letter-spacing: 1px;
margin: 0;
text-shadow: 0 0 6px rgba(255, 255, 255, 0.6);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment