Redux Toolkit (RTK) — официальный способ писать Redux-код. Он решает проблемы классического Redux:
| Проблема | Классический Redux | Redux Toolkit |
|----------|-------------------|---------------|
| Много кода | action types, creators, reducers | Всё в одном slice |
| Иммутабельность | Вручную spread | Immer под капотом |
| Настройка store | Много boilerplate | configureStore |
| DevTools | Ручная настройка | Из коробки |
| Thunks | redux-thunk middleware | Встроено |
npm install @reduxjs/toolkit react-redux> В интерактивном редакторе ниже библиотеки подключены через CDN, установка не требуется.
createSlice объединяет action types, action creators и reducer в одном месте:
import { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter', // Имя для action types
initialState: { value: 0 }, // Начальное состояние
reducers: {
// Каждый редьюсер автоматически получает action creator
increment: (state) => {
state.value += 1 // Можно мутировать! Immer делает иммутабельно
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
},
reset: (state) => {
state.value = 0
}
}
})
// Автоматически созданные action creators
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions
// Редьюсер для store
export default counterSlice.reducerimport { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counterSlice'
import todosReducer from './todosSlice'
const store = configureStore({
reducer: {
counter: counterReducer, // state.counter
todos: todosReducer // state.todos
}
// DevTools, thunk middleware — включены по умолчанию!
})
export default storeimport { Provider } from 'react-redux'
import store from './store'
function App() {
return (
<Provider store={store}>
<Counter />
<TodoList />
</Provider>
)
}import { useSelector, useDispatch } from 'react-redux'
import { increment, decrement, incrementByAmount } from './counterSlice'
function Counter() {
// Читаем состояние
const count = useSelector(state => state.counter.value)
// Получаем dispatch
const dispatch = useDispatch()
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => dispatch(increment())}>+1</button>
<button onClick={() => dispatch(decrement())}>-1</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
</div>
)
}RTK использует Immer под капотом. Это значит, что вы можете "мутировать" state:
// RTK с Immer — выглядит как мутация, но на самом деле иммутабельно
reducers: {
addTodo: (state, action) => {
state.todos.push(action.payload) // OK!
},
updateTodo: (state, action) => {
const todo = state.todos.find(t => t.id === action.payload.id)
if (todo) {
todo.text = action.payload.text // OK!
}
}
}
// Эквивалент без Immer (классический Redux):
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, action.payload]
}src/
├── app/
│ └── store.js # configureStore
├── features/
│ ├── counter/
│ │ ├── counterSlice.js
│ │ └── Counter.jsx
│ └── todos/
│ ├── todosSlice.js
│ └── TodoList.jsx
└── App.jsx// todosSlice.js
export const selectAllTodos = state => state.todos.items
export const selectTodoById = (state, id) =>
state.todos.items.find(todo => todo.id === id)
export const selectCompletedTodos = state =>
state.todos.items.filter(todo => todo.completed)
export const selectTodosCount = state => state.todos.items.length
// Использование
const todos = useSelector(selectAllTodos)
const completedTodos = useSelector(selectCompletedTodos)Полный пример: slice + store + компонент
// Симуляция Redux Toolkit createSlice
function createSlice({ name, initialState, reducers }) {
const actions = {}
const actionTypes = {}
// Создаём action creators автоматически
Object.keys(reducers).forEach(key => {
const type = name + '/' + key
actionTypes[key] = type
actions[key] = (payload) => ({ type, payload })
})
// Создаём reducer
function reducer(state = initialState, action) {
const [sliceName, actionName] = action.type.split('/')
if (sliceName === name && reducers[actionName]) {
// Симуляция Immer — создаём копию и мутируем её
const draft = JSON.parse(JSON.stringify(state))
reducers[actionName](draft, action)
return draft
}
return state
}
return { actions, reducer, name }
}
// Симуляция configureStore
function configureStore({ reducer }) {
const reducers = reducer
let state = {}
// Инициализация состояния
Object.keys(reducers).forEach(key => {
state[key] = reducers[key](undefined, { type: '@@INIT' })
})
const listeners = []
return {
getState: () => state,
dispatch: (action) => {
console.log('⚡ Action:', action.type)
Object.keys(reducers).forEach(key => {
state[key] = reducers[key](state[key], action)
})
listeners.forEach(fn => fn())
},
subscribe: (fn) => {
listeners.push(fn)
return () => listeners.splice(listeners.indexOf(fn), 1)
}
}
}
// === Создаём slice для задач ===
const todosSlice = createSlice({
name: 'todos',
initialState: { items: [], filter: 'all' },
reducers: {
addTodo: (state, action) => {
state.items.push({
id: Date.now(),
text: action.payload,
completed: false
})
},
toggleTodo: (state, action) => {
const todo = state.items.find(t => t.id === action.payload)
if (todo) todo.completed = !todo.completed
},
removeTodo: (state, action) => {
state.items = state.items.filter(t => t.id !== action.payload)
},
setFilter: (state, action) => {
state.filter = action.payload
}
}
})
const { addTodo, toggleTodo, removeTodo, setFilter } = todosSlice.actions
// === Создаём store ===
const store = configureStore({
reducer: {
todos: todosSlice.reducer
}
})
// Подписка на изменения
store.subscribe(() => {
const { todos } = store.getState()
console.log('📋 Todos:', todos.items.map(t =>
(t.completed ? '✅' : '⬜') + ' ' + t.text
))
})
// === Тестируем ===
console.log('=== Redux Toolkit Demo ===\n')
store.dispatch(addTodo('Изучить createSlice'))
store.dispatch(addTodo('Настроить store'))
store.dispatch(addTodo('Подключить к React'))
const firstTodoId = store.getState().todos.items[0].id
store.dispatch(toggleTodo(firstTodoId))
console.log('\nФинальное состояние:', store.getState())Redux Toolkit (RTK) — официальный способ писать Redux-код. Он решает проблемы классического Redux:
| Проблема | Классический Redux | Redux Toolkit |
|----------|-------------------|---------------|
| Много кода | action types, creators, reducers | Всё в одном slice |
| Иммутабельность | Вручную spread | Immer под капотом |
| Настройка store | Много boilerplate | configureStore |
| DevTools | Ручная настройка | Из коробки |
| Thunks | redux-thunk middleware | Встроено |
npm install @reduxjs/toolkit react-redux> В интерактивном редакторе ниже библиотеки подключены через CDN, установка не требуется.
createSlice объединяет action types, action creators и reducer в одном месте:
import { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter', // Имя для action types
initialState: { value: 0 }, // Начальное состояние
reducers: {
// Каждый редьюсер автоматически получает action creator
increment: (state) => {
state.value += 1 // Можно мутировать! Immer делает иммутабельно
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
},
reset: (state) => {
state.value = 0
}
}
})
// Автоматически созданные action creators
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions
// Редьюсер для store
export default counterSlice.reducerimport { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counterSlice'
import todosReducer from './todosSlice'
const store = configureStore({
reducer: {
counter: counterReducer, // state.counter
todos: todosReducer // state.todos
}
// DevTools, thunk middleware — включены по умолчанию!
})
export default storeimport { Provider } from 'react-redux'
import store from './store'
function App() {
return (
<Provider store={store}>
<Counter />
<TodoList />
</Provider>
)
}import { useSelector, useDispatch } from 'react-redux'
import { increment, decrement, incrementByAmount } from './counterSlice'
function Counter() {
// Читаем состояние
const count = useSelector(state => state.counter.value)
// Получаем dispatch
const dispatch = useDispatch()
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => dispatch(increment())}>+1</button>
<button onClick={() => dispatch(decrement())}>-1</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
</div>
)
}RTK использует Immer под капотом. Это значит, что вы можете "мутировать" state:
// RTK с Immer — выглядит как мутация, но на самом деле иммутабельно
reducers: {
addTodo: (state, action) => {
state.todos.push(action.payload) // OK!
},
updateTodo: (state, action) => {
const todo = state.todos.find(t => t.id === action.payload.id)
if (todo) {
todo.text = action.payload.text // OK!
}
}
}
// Эквивалент без Immer (классический Redux):
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, action.payload]
}src/
├── app/
│ └── store.js # configureStore
├── features/
│ ├── counter/
│ │ ├── counterSlice.js
│ │ └── Counter.jsx
│ └── todos/
│ ├── todosSlice.js
│ └── TodoList.jsx
└── App.jsx// todosSlice.js
export const selectAllTodos = state => state.todos.items
export const selectTodoById = (state, id) =>
state.todos.items.find(todo => todo.id === id)
export const selectCompletedTodos = state =>
state.todos.items.filter(todo => todo.completed)
export const selectTodosCount = state => state.todos.items.length
// Использование
const todos = useSelector(selectAllTodos)
const completedTodos = useSelector(selectCompletedTodos)Полный пример: slice + store + компонент
// Симуляция Redux Toolkit createSlice
function createSlice({ name, initialState, reducers }) {
const actions = {}
const actionTypes = {}
// Создаём action creators автоматически
Object.keys(reducers).forEach(key => {
const type = name + '/' + key
actionTypes[key] = type
actions[key] = (payload) => ({ type, payload })
})
// Создаём reducer
function reducer(state = initialState, action) {
const [sliceName, actionName] = action.type.split('/')
if (sliceName === name && reducers[actionName]) {
// Симуляция Immer — создаём копию и мутируем её
const draft = JSON.parse(JSON.stringify(state))
reducers[actionName](draft, action)
return draft
}
return state
}
return { actions, reducer, name }
}
// Симуляция configureStore
function configureStore({ reducer }) {
const reducers = reducer
let state = {}
// Инициализация состояния
Object.keys(reducers).forEach(key => {
state[key] = reducers[key](undefined, { type: '@@INIT' })
})
const listeners = []
return {
getState: () => state,
dispatch: (action) => {
console.log('⚡ Action:', action.type)
Object.keys(reducers).forEach(key => {
state[key] = reducers[key](state[key], action)
})
listeners.forEach(fn => fn())
},
subscribe: (fn) => {
listeners.push(fn)
return () => listeners.splice(listeners.indexOf(fn), 1)
}
}
}
// === Создаём slice для задач ===
const todosSlice = createSlice({
name: 'todos',
initialState: { items: [], filter: 'all' },
reducers: {
addTodo: (state, action) => {
state.items.push({
id: Date.now(),
text: action.payload,
completed: false
})
},
toggleTodo: (state, action) => {
const todo = state.items.find(t => t.id === action.payload)
if (todo) todo.completed = !todo.completed
},
removeTodo: (state, action) => {
state.items = state.items.filter(t => t.id !== action.payload)
},
setFilter: (state, action) => {
state.filter = action.payload
}
}
})
const { addTodo, toggleTodo, removeTodo, setFilter } = todosSlice.actions
// === Создаём store ===
const store = configureStore({
reducer: {
todos: todosSlice.reducer
}
})
// Подписка на изменения
store.subscribe(() => {
const { todos } = store.getState()
console.log('📋 Todos:', todos.items.map(t =>
(t.completed ? '✅' : '⬜') + ' ' + t.text
))
})
// === Тестируем ===
console.log('=== Redux Toolkit Demo ===\n')
store.dispatch(addTodo('Изучить createSlice'))
store.dispatch(addTodo('Настроить store'))
store.dispatch(addTodo('Подключить к React'))
const firstTodoId = store.getState().todos.items[0].id
store.dispatch(toggleTodo(firstTodoId))
console.log('\nФинальное состояние:', store.getState())Создай todo-приложение используя паттерн Redux Toolkit. Реализуй slice с действиями addTodo, toggleTodo, removeTodo, clearCompleted. Добавь фильтрацию: all, active, completed.
addTodo: completed: false. toggleTodo: !todo.completed. removeTodo: t.id !== action.payload. clearCompleted: !t.completed (оставляем незавершённые).