> 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/domashnee-zadanie.md).

# Домашнее задание

### ⚡ 1) Зачистить стейт при вылогинивании

\
Если вылогиниться из приложения, то таски и тудулисты, которые находятся в стейте не зачищаются. И это не есть хорошо.\
В данном [видео ](https://youtu.be/w45bp-zCqCc)описана проблема и ее реализация на redux. Ваша задача сделать это на redux toolkit&#x20;

### &#x20;⚡2) Работа с селекторами&#x20;

Рефакторим все селекторы в приложении согласно **auth.selectors.ts**

<figure><img src="/files/bmbA5j0qBVxHgeGqJgJn" alt=""><figcaption></figcaption></figure>

В [данном видео](https://youtu.be/6H41zRDbq9Y) (запись с урока) максимально подробно рассказываю про работу с селекторами

\-----------------------------------------------------------------------------------------------------

### 🔗[Многие моменты я взял из данной статьи](https://habr.com/ru/articles/564004/)

#### ⚡&#x31;**)  Intro**

Давайте зайдем в компонент **`App.tsx`** и посмотрим как мы достаем данные из стейта

```typescript
const isLoading = useAppSelector((state) => state.app.isLoading);
```

Казалось бы и что тут рассказывать, но на самом деле есть нюансы которые нужно знать

#### ⚡&#x32;**)**  useAppSelector

⚡2.1) Вводная часть

Давайте перепишем доставание **`isLoading`** вот так

```typescript
const { isLoading } = useAppSelector((state) => state.app);
```

Я довольно часто вижу такую запись. Почему так студенты любят писать. Допустим нам из app стейта нужно достать 3 значения (**`isLoading`**, **`isAppInitialized`**, **`unHandleActions`**)

И теперь сравним 2 варианта

{% tabs %}
{% tab title="1 вариант" %}
{% code overflow="wrap" lineNumbers="true" %}

```typescript
const { isLoading, isAppInitialized, unHandleActions } = useAppSelector((state) => state.app);
```

{% endcode %}
{% endtab %}

{% tab title="2 вариант" %}
{% code overflow="wrap" lineNumbers="true" %}

```typescript
const isLoading = useAppSelector((state) => state.app.isLoading);
const isAppInitialized = useAppSelector((state) => state.app.isAppInitialized);
const unHandleActions = useAppSelector((state) => state.app.unHandleActions);
```

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

Сравнивая 1 и 2 вариант, очевидно что в 1 случае кода писать меньше, именно поэтому так и любят писать.

Теперь проверьте, поменялось ли что-нибудь или нет ?&#x20;

Ответ: Нет, ничего не поменялось. Значит получается без разницы как писать? А вот здесь ответ нет, разница существенная.

{% hint style="info" %}
На работе приложения (его логической части) это никак не скажется. А на чем скажется - на перфомансе (как часто наши компоненты перерисовываться).
{% endhint %}

⚡ 2.2) Начнем со **2 варианта**\
Вот такой у нас будет стартовый код

{% code title="App.tsx " overflow="wrap" lineNumbers="true" %}

```tsx
export const App = () => {
  console.log("App render");

  const dispatch = useAppDispatch();

  const isLoading = useAppSelector((state) => state.app.isLoading);
  const isAppInitialized = useAppSelector((state) => state.app.isAppInitialized);
  const unHandleActions = useAppSelector((state) => state.app.unHandleActions);

  // const { isLoading, isAppInitialized, unHandleActions } = useAppSelector((state) => state.app);

  console.log("isLoading: ", isLoading);
  console.log("isAppInitialized: ", isAppInitialized);
  console.log("unHandleActions: ", unHandleActions);

  useEffect(() => {
    setTimeout(() => {
      dispatch(appActions.setIsLoading({ isLoading: true }));
    }, 3000);
  }, []);

  return <div className="App">{isLoading && <LinearProgress />}</div>;
};
```

{% endcode %}

Вот, что мы увидим в консоли

<div align="left"><figure><img src="/files/wytR8go3ooUIk7gKpypH" alt=""><figcaption></figcaption></figure></div>

**`isLoading`** изменил свое состояние, соответственно компонент App перерисовался. Все логично, все хорошо.

* теперь давайте в useEffect через 3 секунды будем менять **error** (вмеcто **`isLoading`**)

```typescript
dispatch(appActions.setAppError({ error: "Error" }));
```

<div align="left"><figure><img src="/files/djeHbnotOpyc7Z883Q8F" alt=""><figcaption></figcaption></figure></div>

&#x20;⚡ 2.3) Теперь все тоже самое проделаем для **1 варианта**

1\) Для первого варианта, когда меняем крутилку ничего не поменялось

2\) Но когда мы меняем ошибку, то получим следующий итог<br>

<div align="left"><figure><img src="/files/4zR1wDRXfRAb11BwbS3e" alt=""><figcaption></figcaption></figure></div>

В **`App.tsx`** мы не достаем  error, но она все равно перерисовывается 🤯🤯🤯

{% hint style="info" %}
Так происходит именно по причине неправильного доставания данных из стейта. Мы подписываемся в useSelector на весь **`app`**\
\
\&#xNAN;**`useAppSelector(state => state.app)`**\
\
Соответственно если в app стейте поменяется любое свойство, то все компоненты в которых данные достаются подобным образом  будут перерисовываться вне зависимости используется там это свойство или нет &#x20;
{% endhint %}

{% hint style="warning" %}

* Берем максимально точечно данные из стейта
* Никогда не используем деструктуризация в селекторах (Можно этим правилом пренебречь в том случае, если уверены, что все свойства стейта используются в компоненте)
  {% endhint %}

#### ⚡&#x33;**)**  Selector&#x20;

Опять возвращаемcя к нашему исходному селектору

```typescript
const isLoading = useAppSelector((state) => state.app.isLoading);
```

⚡3.1) Как правило в разных компонентах нам нужно доставать одинаковые данные из стейта. Т.е. Код я указал выше может использовался в 10 компонентах. \
А теперь представьте что у нас в приложении мы решели заменить свойство **`isLoading`** на **`loadingStatus.`** К чему это приведет?

Это приведет к тому, что нам нужно идти в 10 компонент и менять эти свойства, что не очень приятно.&#x20;

⚡3.2) При каждом рендере - новая функция. Cелектор будет вызываться при каждом рендере, а не только когда обновились данные в сторе.

⚡3.3) Логика получения данных из структуры стора находится внутри компонента.\
Но зачем компоненту знать об этом?

По этим причинам селекторы выносят в отдельные функции, которые в принципе так и называют.

Создадим файл **`app.selector.ts`** и вынесем туда кода доставания значения из стейта

<div align="left"><figure><img src="/files/eDb46NFMcKdzFZTHelua" alt=""><figcaption></figcaption></figure></div>

Функции селекторы называют по разному. Встречал следующие варианты:

* **select**IsLoading
* isLoading**Selector**
* иногда вообще в названии не указывают select (selector) и группируют все селекторы в один объект (namespace). Я так не делал никогда : )

{% hint style="warning" %}

* **Все селекторы выносим в файл с селекторами**
* **Селекторы кладем в отдельные файлы соответствующие фиче.**&#x20;
  {% endhint %}

###

#### ⚡&#x34;**)**  [**Reselect**](https://www.npmjs.com/package/reselect)

Говоря про селекторы нельзя не упомянуть про [**createSelector**](https://redux-toolkit.js.org/api/createSelector)

⚡4.1) Теория

Селекторы можно разделить на 2 типа

* **простые (обычные) селекторы** - это селекторы которые просто достают данные из стейта

```typescript
const selectCount = (state: RootState) => state.counter.value;
const isLoadingSelector = (state: RootState) => state.app.isLoading;
const packsSelector = (state: RootState) => state.packs.cardPacks;
```

Такие селекторы стоит использовать всегда, когда мы напрямую ссылаемся на данные из стора. Даже если возвращаемое значение является объектом, не стоит беспокоиться - мы лишь возвращаем ссылку на уже существующий объект, который находится в стейте, так что ни к каким проблемам это не приведет.

* **сложные селекторы**, это селекторы в которых  мы делаем сортировку / фильтрацию или сложные вычисления

```typescript
const selectSubtotal = createSelector(selectShopItems, items =>
  items.reduce((subtotal, item) => subtotal + item.value, 0)
)

const selectTax = createSelector(
  selectSubtotal,
  selectTaxPercent,
  (subtotal, taxPercent) => subtotal * (taxPercent / 100)
)

const selectTotal = createSelector(
  selectSubtotal,
  selectTax,
  (subtotal, tax) => ({ total: subtotal + tax })
)
```

⚡4.2) Практика

{% hint style="danger" %}
Задача\
&#x20;Отображать только те колоды у которых в имени содержится буква **f**.

* в проекте с карточками поиск, фильтрации, сортировка реализована на сервере, но ради примера давайте будем с сервера запрашивать 100 колод и фильтровать их по имени локально.
  {% endhint %}

1\) Захаркодим запрос за 100 колодами

```typescript
getPacks: () => {
  return instance.get<FetchPacksResponseType>("cards/pack?pageCount=100");
}
```

2\) Вынесем получение колод в селектор

```typescript
import { RootState } from "app/store";

export const packsSelector = (state: RootState) => state.packs.cardPacks;
```

3\) Теперь сделаем сложный селектор в котором собственно и будем осуществлять логику по фильтрации

{% code title="packs.selector.ts" overflow="wrap" lineNumbers="true" %}

```typescript
export const filteredByNamePacksSelector = (state: RootState) => {
  console.log("filteredByNamePacksSelector");
  const packs = state.packs.cardPacks;
  return packs.filter((p) => p.name.includes("f"));
};

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes
```

{% endcode %}

Теперь воспользуйтесь данным селектором в Packs.tsx. Попробуйте фильтровать по разным буквам и убедитесь, что все отрабатывает верно 👍

4\) Для того, чтобы понять что собственно не так, давайте компонент **`Counter`** (мог бы быть абсолютно любой компонент в котором используется useSelector) уберем из роутинга и вставим на одном уровне с App

```typescript
root.render(
  <Provider store={store}>
    <App />
    <Counter />
    <RouterProvider router={router} />
    <GlobalError />
  </Provider>
);
```

5\) Напишем **`console.log('render ${component}')`** в **Packs.tsx** и **Counter.tsx**

И теперь измените меняйте значение Counter

<div align="left"><figure><img src="/files/vVawPpo2MeQeIhTJLBLf" alt=""><figcaption></figcaption></figure></div>

Мы меняем значение счетчика, который вообще никоим боком не связан с колодами, но мы видим как она перерисовывается 🤯🤯🤯. <br>

{% hint style="info" %}
Такое поведение мы получаем из-за того, что наш сложный селектор возвращает новый массив.&#x20;

**useSelector** не просто достает данные из стейта, он еще делает подписку. И рендер в компонентах происходит когда приходит новое значение.

Чтобы сравнить какому компоненту перерисовываться, под капотом у useSelector происходит сравнение изменились данные или нет.

Сравнение под капотом происходит по ссылке, а как мы знаем объекты / массивы сравниваются по ссылке и два одинаковых массива никогда не будут равны.&#x20;

Максимально подробно эту тему освятил АйтиСиняк

* [**Все ли вы знаете о useSelector**](https://youtu.be/SVG-x-4BQic)
* [**Самый скользкий механизм в Redux**](https://youtu.be/tbfo28Q5eag)
  {% endhint %}

6\) И вот чтобы избежать вот такого поведения нам и нужен **`createSelector`**

{% hint style="info" %}
**`createSelector`** - это функция из библиотеки `reselect`, которая позволяет создавать селекторы в Redux-приложениях. Селекторы используются для извлечения данных из хранилища Redux, а `createSelector` помогает оптимизировать производительность приложения, кэшируя результаты вызова селекторов и предотвращая повторные вычисления.&#x20;
{% endhint %}

{% code title="packs.selector.ts" overflow="wrap" lineNumbers="true" %}

```typescript
export const filteredByNamePacksSelector = createSelector(
  // 1 - массив селекторов
  [packsSelector],
  // 2 - функция, которая принимает данные от селекторов и возвращает новое значение
  (packs) => {
    console.log("filteredByNamePacksSelector");
    return packs.filter((p) => p.name.includes("f"));
  }
);
```

{% endcode %}

По итогу получим вот такой результат 🚀

<div align="left"><figure><img src="/files/zTRgd6ARhQsvqjVn61K9" alt=""><figcaption></figcaption></figure></div>

🔗 [**Примеры работы с useSelector из документации**](https://react-redux.js.org/api/hooks#useselector-examples)

☝ Вывод по использованию createSelect такой

1\) **Используйте мемоизированные селекторы когда:**&#x20;

* В селекторе есть тяжелые вычисления (фильтрация, сортировка, сложное преобразование данных, и так далее)
* Результатом вызова селектора является объект. Ну и конечно же, это касается массивов и различных структур вроде Set и Map, так как они тоже являются объектами.

2\) [**Есть мнение о том чтобы без анализа селектора всегда использовать reselect**](https://www.youtube.com/watch?v=qWzxwzcjttk)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://safronman.gitbook.io/rtk-1-nsdfzx/domashnee-zadanie.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
