🦆Redux toolkit
⚡1) Введение
⚡2) Добавляем в существующий проект redux toolkit
https://redux-toolkit.js.org/introduction/getting-started#installation
⚡3) Изменения в store.ts
Store в RTK создается при помощи функции configureStore
В инициализационной конфигурации store уже создан, поэтому летим дальше 🚀
3.1) Теперь создадим store согласно документации @reduxjs/toolkit
3.2) Изменим немного типизацию стейта всего приложения, чтобы соответствовать документации
export type AppRootStateType = ReturnType<typeof store.getState>
3.3) thunk middleware идет по умолчанию, поэтому его не обязательно добавлять. Но если / когда понадобится делаем это следующим образом https://redux-toolkit.js.org/usage/usage-with-typescript#correct-typings-for-the-dispatch-type https://redux-toolkit.js.org/api/getDefaultMiddleware#intended-usage
import thunkMiddleware, { ThunkAction, ThunkDispatch } from "redux-thunk";
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(thunkMiddleware)
})
⚡4) Reducer / slice / createSlice
Теперь давайте научимся создавать редьюсеры в RTK.
В принципе можно создать редьюсер через createReducer, но это применяется редко на практике, поэтому давайте сразу будем делать создание редьюсеров через createSlice
4.1) На основании примера из документации перепишем authReducer на slice
const initialState = {
isLoggedIn: false
}
export const authReducer = (state: InitialStateType = initialState, action: ActionsType): InitialStateType => {
switch (action.type) {
case 'login/SET-IS-LOGGED-IN':
return {
...state,
isLoggedIn: action.value}
default:
return state
}
}
// actions
export const setIsLoggedInAC = (value: boolean) => ({type: 'login/SET-IS-LOGGED-IN', value} as const)
// types
type ActionsType = ReturnType<typeof setIsLoggedInAC>
type InitialStateType = typeof initialState
4.2) По аналогии переводим app-reducer.ts
⌚Самостоятельная работа. Начинаем самостоятельно, с преподавателем продолжаем
❗Т.к. экшены с app используются во многих санка / тестах / утилитных функциях, то рефакторинга будет много. Но ничего страшного, проделываем, набиваем руку. В любом случае сейчас или потом рефакторить придется
❗Если мы не выносим стейт в slice, то у нас будут проблемы с тестами, т.к. в тестах мы используем такой же стейт, но если мы допустим в нем ошибку, то TS не ругнется. Чтобы решить эту проблему и не выносить отдельно стейт напишем следующую типизацию
export type AppInitialStateType = ReturnType<typeof slice.getInitialState>
4.3) По аналогии переводим todolists-reducer.ts
при работе с массивом пользуемся если это возможно мутабельными способами изменения - https://immerjs.github.io/immer/update-patterns/#array-mutations - https://redux-toolkit.js.org/usage/immer-reducers
Например splice, push, pop, unshift...
Т.к. в тудулисте будут не совсем тривиальное изменение стейта, держите подсказки
addTodolist: (state, action: PayloadAction<{ todolist: TodolistType }>) => {
const newTodolist: TodolistDomainType = {...action.payload.todolist, filter: 'all', entityStatus: 'idle'}
state.unshift(newTodolist)
},
changeTodolistTitle: (state, action: PayloadAction<{ id: string, title: string }>) => {
// 1 variant
const index = state.findIndex((todo) => todo.id === action.payload.id)
if (index !== -1) state[index].title = action.payload.title
// 2 variant
const todo = state.find((todo) => todo.id === action.payload.id)
if (todo) {
todo.title = action.payload.title
}
},
setTodolists: (state, action: PayloadAction<{ todolists: TodolistType[] }>) => {
// 1 variant
action.payload.todolists.forEach((tl) => {
state.push({ ...tl, filter: "all", entityStatus: "idle" })
})
// 2 variant
return action.payload.todolists.map(tl => ({...tl, filter: 'all', entityStatus: 'idle'}))
},
❗ Рассказать что менять весь стейт напрямую запрещено
❗ Рассказать как вывести в консоль state
❗ Чтобы протестировать работоспособность:
todolists-reducers
в tasks-reducer.ts в ActionType добавим anyв компоненте Todolist сделаем проверку на undefined c помощью оператора ?
4.4) По аналогии переводим tasks-reducer.ts
не самые проcтые изменения в редьюсерах и для того чтобы переписать некоторые кейсы нам понадобятся знания об
extraReducers
⬇️
⚡5) extraReducers
5.1) Когда нам необходимо обработать case, который был создан в другом slice (т.к. не принадлежит текущему), тогда нам необходимо в функцию createSlice добавить свойство extraReducers
Пример. Мы создали todolists-reducer с помощью createSlice, но когда мы добавляем тудулист, то нам необходимо в task-reducer написать case, который будет добавлять пустой массив тасок в только что созданный тудулист. Т.е. как раз таки и получается, что мы будем task-reducer обрабатывать case, который создан в todolists-reducer
Подсказка
reducers: {
removeTask: (state, action: PayloadAction<{ taskId: string, todolistId: string }>) => {
const tasks = state[action.payload.todolistId]
const index = tasks.findIndex(t => t.id === action.payload.taskId)
if (index !== -1) tasks.splice(index, 1)
},
addTask: (state, action: PayloadAction<{ task: TaskType }>) => {
const tasks = state[action.payload.task.todoListId]
tasks.unshift(action.payload.task)
},
updateTask: (state, action: PayloadAction<{
taskId: string,
model: UpdateDomainTaskModelType,
todolistId: string
}>) => {
const tasks = state[action.payload.todolistId]
const index = tasks.findIndex(t => t.id === action.payload.taskId)
if (index !== -1) {
tasks[index] = {...tasks[index], ...action.payload.model}
}
},
setTasks: (state, action: PayloadAction<{ tasks: Array<TaskType>, todolistId: string }>) => {
state[action.payload.todolistId] = action.payload.tasks
},
},
extraReducers: builder => {
builder
.addCase(todolistsActions.addTodolist, (state, action) => {
state[action.payload.todolist.id] = []
})
.addCase(todolistsActions.removeTodolist, (state, action) => {
delete state[action.payload.id]
})
.addCase(todolistsActions.setTodolists, (state, action) => {
action.payload.todolists.forEach((tl) => {
state[tl.id] = []
})
})
}
5.2) синтаксис extrarReducer
✅ "builder callback" notation (рекомендуемый способ работы с TS)
5.3) Builder предоставляет 3 метода https://redux-toolkit.js.org/api/createReducer#usage-with-the-builder-callback-notation
addCase
addMatcher
addDefaultCase
Last updated
Was this helpful?