изначально опубликовано на almoullim.com

Redux - это библиотека JavaScript с открытым исходным кодом для управления состоянием приложения. ‌‌ Redux Toolkit - это самоуверенный набор инструментов для разработки Redux с включенными батареями.

Redux Toolkit предоставляет набор инструментов, которые помогают нам реализовать Redux более простым (хотя и самоуверенным) способом.

Внедрение Redux в проект осуществляется путем создания действий и редукторов. Действия описывают, какие изменения мы хотим произойти с хранилищем, и редукторы выполняют эти изменения, а затем хранилище Redux уведомляет наши компоненты об изменении значений хранилища, и они повторно визуализируются с обновленными значениями.

Действия - это просто объект JS, содержащий type действие, которое мы хотим выполнить, и payload, который представляет собой данные, необходимые для этого действия.

например, здесь наше действие состоит в том, чтобы добавить задачу, и мы предоставляем данные в полезной нагрузке с информацией о ней:

{
  type: 'ADD_TODO',
  payload: {
    name: 'Buy Groceries',
  },
}

и чтобы еще больше упростить отправку действий, мы можем создать функцию, которая возвращает нам полный объект:

function addTodo(text) {
  return {
    type: 'ADD_TODO',
    payload: { name: text }
  }
}

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

например, чтобы добавить задачу:

const initialState = [
  { name: 'Wash the car'. completed: false }, 
  { name: 'Wash the clothes', completed: false }
]
function todosReducer(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return [...state.todos, {...action.payload, completed: false}]
    default:
      return state
  }
}

В Redux состояние не должно изменяться, мы всегда должны возвращать новое состояние, поэтому мы не делали state.push(action.payload), а вместо этого создали новый массив, используемый для оператора распространения, чтобы скопировать все предыдущие задачи в новый массив и добавить наши новые todo, затем вернул этот новый массив, который является нашим новым состоянием.

После этого мы можем создать больше редукторов и объединить их с помощью функции combineReducers из redux в один корневой редуктор и, наконец, передать его в хранилище:

const rootReducer = combineReducers({
  todosReducer
})
const store = createStore(reducer)

затем мы можем получить текущее состояние магазина, подписаться на магазин и отправлять действия, как нам нравится:

function onStateChange() {
  console.log(store.getState())
}
store.subscribe(onStateChange)
const newTodo = {name: 'Buy Groceries'}
store.dispatch({ type: 'ADD_TODO', payload: newTodo })
// or use the previously created function
store.dispatch(addTodo('Buy Groceries'))

Теперь в Redux есть много лишних шаблонов, и Redux Toolkit забирает у нас большую часть этого шаблона.

Среди других функций он предоставляет нам функцию с именем createSlice, которая обрабатывает логику создания типов действий, создателей действий, редукторов, switch-case / if-else, возврата состояния в случае по умолчанию, возврата нового состояния (дайте нам скопированный состояние, позволяющее нам изменять его напрямую и автоматически возвращать его как новый). Он также предоставляет функцию configureStore, которая объединяет редукторы, включает Redux DevTools и добавляет redux-thunk (для асинхронных действий):

Таким образом, мы можем переписать предыдущий код с помощью Redux Toolkit следующим образом:

const todosSlice = createSlice({
  name: 'todos',
  initialState: [
    { name: 'Wash the car', completed: false },
    { name: 'Wash the clothes', completed: false },
  ],
  reducers: {
    addTodo: (state, action) {
      state.push(action.payload)
    }
  },
})
const store = configureStore({
  reducer: {
    todos: todosSlice
  }
})

Итак, давайте рассмотрим весь предыдущий код, который мы сделали без Redux Toolkit, и создадим остальные действия, пока мы на нем:

import { createStore, combineReducers } from 'redux'
const ADD_TODO = 'ADD_TODO'
const REMOVE_TODO = 'REMOVE_TODO'
const TOGGLE_TODO = 'TOGGLE_TODO'
function addTodo(text) {
  return {
    type: 'ADD_TODO',
    payload: { name: text }
  }
}
function removeTodo(index) {
  return {
    type: 'REMOVE_TODO',
    payload: { index }
  }
}
function toggleTodo(index) {
  return {
    type: 'TOGGLE_TODO',
    payload: { index }
  }
}
const initialState = [
  { name: 'Wash the car', completed: false },
  { name: 'Wash the clothes', completed: false },
]
function todosReducer(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return [...state, action.payload]
    case REMOVE_TODO:
      return state.filter((todo, index) => {
        if (index === action.payload.index) {
          return false
        }
        return true
      })
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.payload.index) {
          return {
            ...todo,
            completed: !todo.completed,
          }
        }
        return todo
      })
    default:
      return state
  }
}
const rootReducer = combineReducers({
  todosReducer
})
const store = createStore(reducer)
export default store
export {
    addTodo,
    removeTodo,
    toggleTodo
}
// To test that the store is working
function onStateChange() {
  console.log(store.getState())
}
store.subscribe(onStateChange)
store.dispatch(addTodo('Buy Groceries'))

А теперь перепишем его с помощью Redux Toolkit:

import { createSlice, configureStore } from '@reduxjs/toolkit'
const todosSlice = createSlice({
  name: 'todos',
  initialState: [
    { name: 'Wash the car', completed: false },
    { name: 'Wash the clothes', completed: false },
  ],
  reducers: {
    addTodo(state, action) {
      state.push(action.payload)
    },
    removeTodo(state, action) {
     state.splice(action.payload.index, 1)
    },
    toggleTodo(state, action) {
     state[index].completed = !state[index].completed
    },
  },
})
const store = configureStore({
  reducer: {
    todos: todosSlice
  }
})
export default store
export const { addTodo, removeTodo, toggleTodo } = todosSlice.actions
// To test that the store is working
function onStateChange() {
  console.log(store.getState())
}
store.subscribe(onStateChange)
store.dispatch(todosSlice.actions.addTodo('Buy Groceries'))

Думайте о createSlice срезах как о модулях хранилища, и вы можете создать столько модулей (срезов), сколько вам нужно, а затем добавить их к объекту, который вы передаете в configureStore

Теперь вы можете приступить к интеграции вашего магазина Redux с React с помощью React Redux, обернув ваше приложение магазином Provider:

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
const rootElement = document.getElementById('root')
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
)

И соединяем ваши компоненты с connect:

import { connect } from 'react-redux'
import { addTodo, removeTodo, toggleTodo } from './store'
// const Todos = ...
const mapStateToProps = (state /*, ownProps*/) => ({
  todos: state.todos,
})
// "object shorthand" form of mapDispatch with action creators
const mapDispatchToProps = { addTodo, removeTodo, toggleTodo }
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Todos)

Не забудьте npm install ... (redux входит в @reduxjs/toolkit):

npm install @reduxjs/toolkit react-redux