HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Перейдем к Redux Toolkit и соберем простое приложение с несколькими кнопками - они будут менять значение счетчика. На этом примере мы увидим основные концепции Redux Toolkit. Для интеграции нам понадобятся два пакета:</p>
1 <p>Перейдем к Redux Toolkit и соберем простое приложение с несколькими кнопками - они будут менять значение счетчика. На этом примере мы увидим основные концепции Redux Toolkit. Для интеграции нам понадобятся два пакета:</p>
2 <ul><li>Пакет<em>react-redux</em></li>
2 <ul><li>Пакет<em>react-redux</em></li>
3 <li>Сам Redux Toolkit из пакета<em>@reduxjs/toolkit</em></li>
3 <li>Сам Redux Toolkit из пакета<em>@reduxjs/toolkit</em></li>
4 </ul><p>Перейдем к установке:</p>
4 </ul><p>Перейдем к установке:</p>
5 <p>Изучим структуру директорий, от которой будем отталкиваться. Это самый простой вариант, но не единственный возможный:</p>
5 <p>Изучим структуру директорий, от которой будем отталкиваться. Это самый простой вариант, но не единственный возможный:</p>
6 <p>Для работы с Toolkit мы выделили директорию<em>slices</em>с двумя файлами внутри -<em>index.js</em>и<em>counterSlice.js</em>.</p>
6 <p>Для работы с Toolkit мы выделили директорию<em>slices</em>с двумя файлами внутри -<em>index.js</em>и<em>counterSlice.js</em>.</p>
7 <p>В файле<em>index.js</em>мы будем инициализировать хранилище и объединять все редьюсеры. Сами редьюсеры разбиваются по отдельным файлам -<strong>слайсам</strong>или<strong>срезам</strong>. Каждый слайс отвечает за определенную сущность в приложении. Например, в приложении со списком товаров и покупателей можно выделить два слайса: один для товаров, другой для покупателей.</p>
7 <p>В файле<em>index.js</em>мы будем инициализировать хранилище и объединять все редьюсеры. Сами редьюсеры разбиваются по отдельным файлам -<strong>слайсам</strong>или<strong>срезам</strong>. Каждый слайс отвечает за определенную сущность в приложении. Например, в приложении со списком товаров и покупателей можно выделить два слайса: один для товаров, другой для покупателей.</p>
8 <p>Такой подход разделяет общее состояние на отдельные модули со своей зоной ответственности. В нашем примере в состоянии находится счетчик, поэтому мы определили для него слайс<em>counterSlice.js</em>. Теперь создаем редьюсер в слайсе для счетчика:</p>
8 <p>Такой подход разделяет общее состояние на отдельные модули со своей зоной ответственности. В нашем примере в состоянии находится счетчик, поэтому мы определили для него слайс<em>counterSlice.js</em>. Теперь создаем редьюсер в слайсе для счетчика:</p>
9 <p>Чтобы создать редьюсер, создаем начальное значение initialState и вызываем createSlice(), который выполняет всю работу. Эта функция принимает объект, в котором нам важны три свойства:</p>
9 <p>Чтобы создать редьюсер, создаем начальное значение initialState и вызываем createSlice(), который выполняет всю работу. Эта функция принимает объект, в котором нам важны три свойства:</p>
10 <ul><li>name задает имя слайса</li>
10 <ul><li>name задает имя слайса</li>
11 <li>initialState задает начальное состояние</li>
11 <li>initialState задает начальное состояние</li>
12 <li>reducers принимает объект, в котором каждое свойство содержит редьюсеры. С их помощью мы будем менять состояние</li>
12 <li>reducers принимает объект, в котором каждое свойство содержит редьюсеры. С их помощью мы будем менять состояние</li>
13 </ul><p>Вызов createSlice() вернет готовый слайс - это объект, в котором нам важны два свойства:</p>
13 </ul><p>Вызов createSlice() вернет готовый слайс - это объект, в котором нам важны два свойства:</p>
14 <ul><li><p>actions - это действия, с помощью которых мы запускаем созданные редьюсеры. Названия действий совпадают с ключами, которые мы указали в reducers при создании слайса. Toolkit автоматически создаст нужные действия и даст строковые имена их типам.</p>
14 <ul><li><p>actions - это действия, с помощью которых мы запускаем созданные редьюсеры. Названия действий совпадают с ключами, которые мы указали в reducers при создании слайса. Toolkit автоматически создаст нужные действия и даст строковые имена их типам.</p>
15 <p>В примере выше мы экспортируем объект с действиями, которые получили из этого свойства. Дальше можно импортировать действия в компонентах, чтобы вызывать их.</p>
15 <p>В примере выше мы экспортируем объект с действиями, которые получили из этого свойства. Дальше можно импортировать действия в компонентах, чтобы вызывать их.</p>
16 </li>
16 </li>
17 <li><p>reducer - это готовый редьюсер, который мы будем подключать в хранилище. В примере выше он экспортируется по умолчанию просто для удобства, чтобы разграничить экспорт экшенов и редьюсера.</p>
17 <li><p>reducer - это готовый редьюсер, который мы будем подключать в хранилище. В примере выше он экспортируется по умолчанию просто для удобства, чтобы разграничить экспорт экшенов и редьюсера.</p>
18 </li>
18 </li>
19 </ul><p>Теперь редьюсер готов к использованию. Сначала подключим его в общее хранилище. Для этого передаем редьюсер в функцию configureStore().</p>
19 </ul><p>Теперь редьюсер готов к использованию. Сначала подключим его в общее хранилище. Для этого передаем редьюсер в функцию configureStore().</p>
20 <p>Эта функция умеет комбинировать редьюсеры самостоятельно, в отличие от такой же функции в Redux. Функция configureStore() принимает на вход объект с ключом reducer, значением которого становится объект с редьюсерами. У общего состояния state ключи будут такими же, как у этого объекта. Более подробно мы это разберем чуть позже. Создание хранилища выглядит так:</p>
20 <p>Эта функция умеет комбинировать редьюсеры самостоятельно, в отличие от такой же функции в Redux. Функция configureStore() принимает на вход объект с ключом reducer, значением которого становится объект с редьюсерами. У общего состояния state ключи будут такими же, как у этого объекта. Более подробно мы это разберем чуть позже. Создание хранилища выглядит так:</p>
21 <p>Здесь мы вызываем функцию configureStore() и передаем в нее объект со свойством reducer. А вот уже в reducer мы указываем объект с нашими редьюсерами. В нашем примере есть единственный редьюсер counterReducer, который мы импортируем по умолчанию из<em>counterSlice.js</em>.</p>
21 <p>Здесь мы вызываем функцию configureStore() и передаем в нее объект со свойством reducer. А вот уже в reducer мы указываем объект с нашими редьюсерами. В нашем примере есть единственный редьюсер counterReducer, который мы импортируем по умолчанию из<em>counterSlice.js</em>.</p>
22 <p>Если бы у нас было несколько слайсов, можно было бы указать их под разными ключами. Это выглядело бы так:</p>
22 <p>Если бы у нас было несколько слайсов, можно было бы указать их под разными ключами. Это выглядело бы так:</p>
23 <p>Выше мы видим два редьюсера, которые передаются под ключами users и tasks. Мы можем придумывать любые имена ключей, но лучше, когда имена соответствуют содержимому. Например, редьюсер для списка пользователей лучше назвать users, а для списка задач - tasks.</p>
23 <p>Выше мы видим два редьюсера, которые передаются под ключами users и tasks. Мы можем придумывать любые имена ключей, но лучше, когда имена соответствуют содержимому. Например, редьюсер для списка пользователей лучше назвать users, а для списка задач - tasks.</p>
24 <p>Теперь наше хранилище готово к использованию. Можно подключить его в приложение.</p>
24 <p>Теперь наше хранилище готово к использованию. Можно подключить его в приложение.</p>
25 <p>Начнем создавать приложение с верхнего уровня. Здесь понадобится компонент &lt;Provider&gt;, который содержит хранилище и прокидывает его вглубь дерева компонентов через контекст:</p>
25 <p>Начнем создавать приложение с верхнего уровня. Здесь понадобится компонент &lt;Provider&gt;, который содержит хранилище и прокидывает его вглубь дерева компонентов через контекст:</p>
26 <p>Теперь самое главное - Toolkit в действии. Здесь мы опираемся на работу хуков. Здесь все работает по-прежнему:</p>
26 <p>Теперь самое главное - Toolkit в действии. Здесь мы опираемся на работу хуков. Здесь все работает по-прежнему:</p>
27 <ul><li>Чтобы изменить состояние в хранилище, передаем действие в функцию dispatch()</li>
27 <ul><li>Чтобы изменить состояние в хранилище, передаем действие в функцию dispatch()</li>
28 <li>Чтобы получить объект dispatch в компоненте, используем функцию useDispatch()</li>
28 <li>Чтобы получить объект dispatch в компоненте, используем функцию useDispatch()</li>
29 <li>Чтобы извлечь данные из стора, используем хук useSelector(). Он принимает функцию, в которую все состояние передается через параметр. Возвращаемое значение из этой функции станет результатом выполнения useSelector()</li>
29 <li>Чтобы извлечь данные из стора, используем хук useSelector(). Он принимает функцию, в которую все состояние передается через параметр. Возвращаемое значение из этой функции станет результатом выполнения useSelector()</li>
30 </ul><p>Посмотрим, как это работает:</p>
30 </ul><p>Посмотрим, как это работает:</p>
31 <p>В компоненте может быть несколько вызовов useSelector, причем каждый вызов создаст подписку на изменение состояния. Срабатывание нескольких подписок одновременно приведет только к одной перерисовке компонента.</p>
31 <p>В компоненте может быть несколько вызовов useSelector, причем каждый вызов создаст подписку на изменение состояния. Срабатывание нескольких подписок одновременно приведет только к одной перерисовке компонента.</p>
32 <p>Обратите внимание, что переданная в useSelector функция принимает все состояние целиком. Если у нас несколько редьюсеров и слайсов, состояние содержит все состояния этих слайсов. Состояние хранится в объекте, где каждый ключ - это то, что мы указали в reducer при создании стора. В нашем случае это свойство counter:</p>
32 <p>Обратите внимание, что переданная в useSelector функция принимает все состояние целиком. Если у нас несколько редьюсеров и слайсов, состояние содержит все состояния этих слайсов. Состояние хранится в объекте, где каждый ключ - это то, что мы указали в reducer при создании стора. В нашем случае это свойство counter:</p>
33 <p>Рассмотрим интерактивный пример:</p>
33 <p>Рассмотрим интерактивный пример:</p>
34 <p><a>https://codepen.io/hexlet/pen/bGodExN</a></p>
34 <p><a>https://codepen.io/hexlet/pen/bGodExN</a></p>
35 <p>Часть этой инициализации делается один раз и почти не меняется. Основной код приложения будет добавляться в компонентах и слайсах. Благодаря слайсам и мутации данных внутри редьюсеров, кода будет значительно меньше, чем в чистом Redux. Об этом мы подробнее поговорим в следующем уроке.</p>
35 <p>Часть этой инициализации делается один раз и почти не меняется. Основной код приложения будет добавляться в компонентах и слайсах. Благодаря слайсам и мутации данных внутри редьюсеров, кода будет значительно меньше, чем в чистом Redux. Об этом мы подробнее поговорим в следующем уроке.</p>
36 <p>Осталось разобрать подключение мидлвар:</p>
36 <p>Осталось разобрать подключение мидлвар:</p>
37 <p>Мидлвары подключаются через свойство middleware. В это свойство мы записываем функцию, которая в свою очередь принимает другую функцию getDefaultMiddleware(). Вызвав ее, мы получаем список текущих мидлвар. К этим мидлварам добавляем наши мидлвары и возвращаем новый список.</p>
37 <p>Мидлвары подключаются через свойство middleware. В это свойство мы записываем функцию, которая в свою очередь принимает другую функцию getDefaultMiddleware(). Вызвав ее, мы получаем список текущих мидлвар. К этим мидлварам добавляем наши мидлвары и возвращаем новый список.</p>