> For the complete documentation index, see [llms.txt](https://safronman.gitbook.io/rtk-1-nsdfzx/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://safronman.gitbook.io/rtk-1-nsdfzx/redux-toolkit.md).

# Redux toolkit

### ⚡**1)  Введение**

**🔗**[ **Официальная документация**](https://redux-toolkit.js.org/)

{% hint style="info" %}
**Redux Toolkit** - это набор инструментов, разработанный командой Redux для упрощения и ускорения процесса разработки приложений на React с использованием Redux. Он предоставляет удобные абстракции над стандартным Redux API, которые позволяют сократить количество бойлерплейта, необходимого для настройки хранилища Redux и управления его состоянием.

Одной из основных проблем, которую решает Redux Toolkit, является упрощение процесса создания Redux-хранилища и управления его состоянием. Вместо того, чтобы создавать отдельные действия (actions), редьюсеры (reducers) и селекторы (selectors) вручную, Redux Toolkit предоставляет готовые инструменты для автоматической генерации всех этих элементов, используя более простой и интуитивно понятный API.

Кроме того, Redux Toolkit также предоставляет множество дополнительных функций, таких как middleware, thunk-функции и immer, которые помогают улучшить производительность, расширяемость и поддерживаемость кода, написанного с использованием Redux.
{% endhint %}

### ⚡**2)**  Добавляем в существующий проект redux toolkit

<https://redux-toolkit.js.org/introduction/getting-started#installation>

### ⚡**3)**  Изменения в **store.ts**

Store в RTK создается при помощи функции **configureStore**

* [configureStore API](https://redux-toolkit.js.org/api/configureStore)
* [пример создания store в Quick Start](https://redux-toolkit.js.org/tutorials/quick-start#create-a-redux-store)

{% hint style="info" %}
**configureStore** - это функция, предоставляемая Redux Toolkit, которая делает настройку Redux store (хранения состояния приложения). Она облегчает создание store и добавляет некоторые полезные функции по умолчанию.

Например, **configureStore** автоматически включает проверку типов данных (type checking) с помощью пакета Redux Toolkit "**redux-immutable-state-invariant**". Это означает, что если вы случайно измените неизменяемое состояние в своем приложении, Redux Toolkit выдаст предупреждение об этом.

Также **configureStore** позволяет вам настраивать **middleware** (промежуточное программное обеспечение), которое позволяет вам добавлять дополнительную функциональность к вашему приложению, например, логирование или обработку ошибок.

Кроме того, **configureStore** автоматически включает **Redux DevTools Extension**, которое позволяет вам отслеживать состояние вашего приложения и действия пользователя в браузере.

Таким образом, **configureStore** облегчает настройку Redux store в вашем приложении и добавляет множество полезных функций по умолчанию, которые могут помочь улучшить производительность и надежность вашего приложения.
{% endhint %}

В инициализационной конфигурации store уже создан, поэтому летим дальше 🚀<br>

3.1) Теперь создадим **store** согласно [документации ](https://redux-toolkit.js.org/tutorials/quick-start#create-a-redux-store)**`@reduxjs/toolkit`** \
\
\
3.2) Изменим немного типизацию стейта всего приложения, чтобы соответствовать документации

```typescript
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>&#x20;

<pre class="language-typescript" data-title="store.ts" data-overflow="wrap" data-line-numbers><code class="lang-typescript">import thunkMiddleware, { ThunkAction, ThunkDispatch } from "redux-thunk";

export const store = configureStore({
<strong>  reducer: rootReducer,
</strong>  middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(thunkMiddleware)
})
</code></pre>

### ⚡**4)** Reducer / slice / [createSlice](https://redux-toolkit.js.org/api/createSlice)

Теперь давайте научимся создавать редьюсеры в RTK.

В принципе можно создать редьюсер через [**createReducer**](https://redux-toolkit.js.org/api/createReducer), но это применяется редко на практике, поэтому давайте сразу будем делать создание редьюсеров через [**createSlice**](https://redux-toolkit.js.org/api/createSlice)

{% hint style="info" %}
**createSlice** - это функция, которая позволяет вам быстро создавать "slice" (часть) вашего глобального состояния в Redux.

"Slice" - это кусок вашего хранилища (store), который содержит данные и функции для их изменения. Например, если у вас есть приложение с заметками, то "slice" может содержать все заметки и функции для добавления/удаления заметок.

createSlice позволяет вам определить начальное состояние "slice" и функции, называемые "reducers", которые изменяют состояние. Кроме того, она автоматически генерирует действия (actions) для каждого редьюсера, которые вы можете использовать в своем приложении.

Например, если вы хотите создать "slice" для списка заметок, вы можете использовать createSlice для определения начального состояния (например, пустого массива заметок) и редьюсеров, таких как addNote и deleteNote, которые будут добавлять или удалять заметки из списка. Затем вы можете использовать созданные действия (actions), такие как "addNote" или "deleteNote", в своем компоненте для обновления списка заметок.

В целом, createSlice упрощает процесс создания "slice" в Redux и сокращает количество кода, необходимого для создания начального состояния, редьюсеров и действий. Это может помочь ускорить разработку вашего приложения и улучшить его производительность.
{% endhint %}

\
4.1) На основании примера из документации перепишем **authReducer** на slice

{% tabs %}
{% tab title="redux" %}
{% code title="auth-reducer.ts" overflow="wrap" lineNumbers="true" %}

```typescript
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

```

{% endcode %}
{% endtab %}

{% tab title="redux toolkit" %}
{% code title="auth-reducer.ts" overflow="wrap" lineNumbers="true" %}

```typescript
import {createSlice, PayloadAction} from '@reduxjs/toolkit';

// slice - редьюсеры создаем с помощью функции createSlice
const slice = createSlice({
   // важно чтобы не дублировалось, будет в качетве приставки согласно соглашению redux ducks
   name: 'auth',
   initialState: {
       isLoggedIn: false
   },
   // состоит из подредьюсеров, каждый из которых эквивалентен одному оператору case в switch, как мы делали раньше (обычный redux)
   reducers: {
       // Объект payload. Типизация через PayloadAction
       setIsLoggedIn: (state, action: PayloadAction<{ isLoggedIn: boolean }>) => {
           // логику в подредьюсерах пишем мутабельным образом,
           // т.к. иммутабельность достигается благодаря immer.js
           state.isLoggedIn = action.payload.isLoggedIn
       }
   }
})

// Создаем reducer с помощью slice
export const authReducer = slice.reducer;
// Action creator также достаем с помощью slice
export const {setIsLoggedIn} = slice.actions
// либо вот так. ❗Делаем так, в дальнейшем пригодиться 
export const authActions = slice.actions

```

{% endcode %}
{% endtab %}
{% endtabs %}

4.2) По аналогии переводим **app-reducer.ts**

⌚Самостоятельная работа. Начинаем самостоятельно, с преподавателем продолжаем

\
❗Т.к. экшены с app используются во многих санка / тестах / утилитных функциях, то рефакторинга будет много. Но ничего страшного, проделываем, набиваем руку. В любом случае сейчас или потом рефакторить придется\
\
❗Если мы не выносим стейт в slice, то у нас будут проблемы с тестами, т.к. в тестах мы используем такой же стейт, но если мы допустим в нем ошибку, то TS не ругнется. Чтобы решить эту проблему и не выносить отдельно стейт напишем следующую типизацию\
\
**`export type AppInitialStateType = ReturnType<typeof slice.getInitialState>`**<br>

4.3) По аналогии переводим **todolists-reducer.ts**

* при работе с массивом пользуемся если это возможно мутабельными способами изменения\
  \- <https://immerjs.github.io/immer/update-patterns/#array-mutations>\
  \- <https://redux-toolkit.js.org/usage/immer-reducers>

Например [splice](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/splice), push, pop, unshift...

Т.к. в тудулисте будут не совсем тривиальное изменение стейта, держите подсказки

{% code title="todolists.reducer.ts" overflow="wrap" lineNumbers="true" fullWidth="true" %}

```typescript
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'}))
},
```

{% endcode %}

❗ Рассказать что [менять весь стейт напрямую запрещено ](https://redux-toolkit.js.org/usage/immer-reducers#resetting-and-replacing-state)

{% hint style="info" %}
Распространенная ошибка заключается в попытке напрямую присвоить state = someValue. Это не сработает! Это лишь указывает локальную переменную state на другую ссылку. Таким образом, не происходит ни мутации существующего объекта/массива состояния в памяти, ни возвращения совершенно нового значения, поэтому Immer не вносит никаких реальных изменений.
{% endhint %}

❗ Рассказать как [вывести в консоль state](https://redux-toolkit.js.org/usage/immer-reducers#debugging-and-inspecting-drafted-state)

❗ Чтобы протестировать работоспособность:

* **`todolists-reducers`** в **tasks-reducer.ts** в **ActionType добавим any**
* в компоненте Todolist сделаем проверку на undefined c помощью оператора ?

4.4) По аналогии переводим **tasks-reducer.ts**

* не самые проcтые изменения в редьюсерах и для того чтобы переписать некоторые кейсы нам понадобятся знания об **`extraReducers`** ⬇️

### ⚡&#x35;**)** [extraReducers ](https://redux-toolkit.js.org/api/createSlice#extrareducers)

5.1) Когда нам необходимо обработать case, который был создан в другом slice (т.к. не принадлежит текущему), тогда нам необходимо в функцию createSlice добавить свойство **extraReducers**

\
**Пример**. \
Мы создали todolists-reducer с помощью createSlice, но когда мы добавляем тудулист, то нам необходимо в task-reducer написать case,  который будет добавлять пустой массив тасок в только что созданный тудулист. Т.е. как раз таки и получается, что мы будем task-reducer  обрабатывать case, который создан в todolists-reducer

Подсказка

{% code title="tasks.reducer.ts" overflow="wrap" lineNumbers="true" fullWidth="true" %}

```typescript
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] = []
	  })
	})
}
```

{% endcode %}

5.2) синтаксис extrarReducer&#x20;

* ✅ [**"builder callback" notation**](https://redux-toolkit.js.org/api/createSlice#the-extrareducers-builder-callback-notation) (рекомендуемый способ работы с TS)
* #### ❌ "map object" notation (удален с выходм версии 2.0) <a href="#the-extrareducers-map-object-notation" id="the-extrareducers-map-object-notation"></a>

5.3) **Builder** предоставляет 3 метода\
<https://redux-toolkit.js.org/api/createReducer#usage-with-the-builder-callback-notation>

* addCase
* addMatcher
* addDefaultCase
