Redux е популярна библиотека за управление на глобалното състояние в уеб приложения. Той осигурява предвидим и последователен начин за актуализиране на състоянието въз основа на действия, изпратени от компоненти. Redux обаче идва и с някои предизвикателства и недостатъци, като например:
- Писане на много шаблонен код за създаване на действия, редуктори, селектори и магазин
- Справяне с неизменни актуализации в редуктори
- Работа с асинхронна логика с междинен софтуер
- Конфигуриране и отстраняване на грешки в магазина с инструменти за разработка
За да се справи с тези проблеми и да направи разработката на Redux по-лесна и по-бърза, екипът на Redux създаде Redux Toolkit (RTK), официален пакет, който съдържа инструменти за опростяване на често срещани задачи при използване на Redux. RTK включва помощни програми за:
- Създаване на редуктори, действия, селектори и части от състояние с по-малко код
- Актуализиране на състоянието неизменно с помощта на библиотеката Immer
- Боравене с асинхронна логика с thunk мидълуер
- Настройване на магазина с мидълуер по подразбиране и инструменти за разработка
В тази статия ще проучим как RTK може да ни помогне да пишем по-добри Redux приложения и да ускорим процеса на разработка.
Създаване на редуктори, действия, селектори и срезове
Един от основните източници на шаблонен код в Redux е създаването на редуктори, действия, селектори и части от състояние. Редукторът е функция, която приема текущото състояние и действие като аргументи и връща ново състояние, действието е обект, който описва какво се е случило в приложението, селекторът е функция, която приема състоянието като аргумент и връща някои производни данни, а срезът е част от състоянието, което съответства на конкретна функция или домейн.
RTK предоставя функции createReducer, createAction, createSelector и createSlice, които могат да ни помогнат да създадем тези обекти с по-малко код. Например,
// Without RTK const INCREMENT = 'counter/increment'; const DECREMENT = 'counter/decrement'; function increment() { return { type: INCREMENT }; } function decrement() { return { type: DECREMENT }; } function counter(state = 0, action) { switch (action.type) { case INCREMENT: return state + 1; case DECREMENT: return state - 1; default: return state; } } function selectCount(state) { return state.counter; } // With RTK import { createReducer , createAction , createSelector , createSlice } from '@reduxjs/toolkit'; const increment = createAction('counter/increment'); const decrement = createAction('counter/decrement'); const counter = createReducer(0 , { [increment]: (state) => state + 1, [decrement]: (state) => state - 1, }); const selectCount = createSelector( (state) => state.counter, (counter) => counter ); // Or even simpler with createSlice const counterSlice = createSlice({ name: 'counter', initialState: 0, reducers: { increment: (state) => state + 1, decrement: (state) => state - 1, }, }); const { actions , reducer } = counterSlice; const { increment , decrement } = actions;
Както можем да видим от този пример,
createAction
генерира функция за създаване на действие, която връща обект на действие с даден тип.createReducer
приема стойност на първоначално състояние и обект от редуценти на регистър, зададен от типове действия. Той използва библиотека Immer вътрешно, за да ни позволи да пишем променлива логика за актуализиране, без да променяме действителното състояние.createSelector
създава мемоизирана селекторна функция, която може ефективно да изчислява извлечени данни от състоянието.createSlice
комбинираcreateAction
иcreateReducer
в една функция, която създава обект на срез, съдържащ както действия, така и редуктор.
Като използваме тези функции от RTK, можем да избегнем писането на повтарящ се код за дефиниране на типове действия, да напишем по-проста логика за актуализиране, без да се притесняваме за неизменността, да оптимизираме производителността на нашите селектори, като кешираме техните резултати и да организираме нашия код по функция или домейн по-лесно.
Неизменно актуализиране на състоянието с помощта на Immer библиотека
Друго предизвикателство при разработката на Redux е неизменното актуализиране на състоянието в редуктори. Неизменните актуализации означават, че не можем директно да модифицираме съществуващия обект на състояние, а вместо това трябва да създадем нов с актуализирани стойности. Това гарантира, че няма да въвеждаме грешки или несъответствия в нашето приложение поради случайни мутации.
Неизменните актуализации обаче могат да бъдат досадни и податливи на грешки при писане, особено когато се работи с вложени обекти или масиви.
Например,
// Without Immer function todoReducer(state = [], action) { switch (action.type) { case 'ADD TODO’: return […state, action.payload]; case ‘TOGGLE_TODO’: return state.map(todo => todo.id === action.payload ? { …todo, completed: !todo.completed } : todo); case ‘REMOVE_TODO’: return state.filter(todo => todo.id !== action.payload); default: return state; } } // With Immer import produce from ‘immer’; function todoReducer(state = [], action) { return produce(state, draft => { switch (action.type) { case ‘ADD_TODO’: draft.push(action.payload); break; case ‘TOGGLE_TODO’: const todo = draft.find(todo => todo.id === action.payload); if (todo) { todo.completed = !todo.completed; } break; case ‘REMOVE_TODO’: const index = draft.findIndex(todo => todo.id === action.payload); if (index > -1) { draft.splice(index, 1); } break; } }); }
Както можем да видим от този пример,
- `produce` е функция от библиотеката на Immer, която приема оригиналното състояние и функция за обратно извикване като аргументи.
- Функцията за обратно извикване получава "чернова" версия на състоянието, която може да бъде мутирана безопасно, без да се засяга първоначалното състояние .
- `produce` връща нов обект на състояние с приложените мутации.
Използвайки „produce“ от Immer, можем да напишем по-проста и по-разбираема логика за актуализиране, без да се притесняваме за неизменността, да избягваме създаването на ненужни копия на обекти или масиви и да използваме вградените методи на JavaScript за манипулиране на структури от данни.
Обработка на асинхронна логика с Thunk Middleware
Друго предизвикателство при разработката на Redux е обработката на асинхронна логика с междинен софтуер. Мидълуер са функции, които прихващат всяко действие, изпратено до магазина, и могат да го променят, забавят, отменят или изпълняват допълнителни задачи, преди да го предадат на следващия мидълуер или редуктор. Мидълуерът е полезен за добавяне на допълнителна функционалност като регистриране, отстраняване на грешки, обработка на грешки или асинхронна логика.
Един от най-честите случаи на използване на мидълуер е обработката на асинхронна логика, като извличане на данни от API. Писането на асинхронна логика с междинен софтуер обаче може да бъде сложно и многословно, особено когато се работи с множество действия за различни етапи на асинхронна операция, като заявка, успех, неуспех, или когато се управляват странични ефекти, като индикатори за зареждане, съобщения за грешка, кеширане и т.н.
С помощта на createAsyncThunk
и createSlice
от RTK,
можем да напишем по-малко шаблонен код за дефиниране на асинхронни действия и обработката им в редуктори, да използваме поддръжката на TypeScript за извеждане на типове действия и полезни товари и да се възползваме от неизменната логика за актуализиране на Immer под капака.
RTK предоставя и други функции, като напр
configureStore
, който създава хранилище Redux с някакъв стандартен междинен софтуер, като thunk, сериализируема проверка, неизменна проверка и т.н.createEntityAdapter
, който създава многократно използвани селектори и редуктори за управление на нормализирани данни в хранилището.createSelector
, който създава мемоизирани селектори за оптимизиране на ефективността на изчисленията на извлечени данни.
В заключение, RTK е предназначен да помогне на разработчиците да пишат по-добър Redux код по-бързо и по-лесно. Не е задължително да използвате RTK с Redux, но е силно препоръчително за повечето случаи на употреба.
Ако искате да научите повече за RTK, можете да посетите официалната му документация на адрес https://redux-toolkit.js.org/