Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Created June 12, 2025 12:04
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 - это разновидность мидлвейр, полезен для написания асинхронных действий (например, API-запросов)
// вообще, thunk (глухой звук) - у программистов означает ткусок кода, который выполняет некую отложенную работу https://daveceddia.com/what-is-a-thunk/
// cлайс (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())}>
decrement
</button>
<button onClick={() => dispatch(increment())}>
increment
</button>
<p>count is {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