【状态管理器】react状态管理器redux-toolkit简单介绍

618 阅读3分钟

react状态管理器redux-toolkit简单介绍

Redux 简单回顾一下

  • 用户点击或者发生某些事件触发 action ,例如dispatch({type: 'counter/increment'})
  • state 和 action 通过 reducer函数产生新的 state
  • store得到新的state通知所有订阅的 UI 进行更新
  • 订阅store数据的 UI 组件检查它们需要的状态是否更改
  • 每个看到其数据已更改的组件都会强制使用新数据重新渲染

Redux Toolkit 诞生的目的

Redux Toolkit旨在成为编写Redux逻辑的标准方式。解决Redux三个常见问题:

  • 配置store太复杂
  • 添加很多包才能让 Redux 做任何有用的事情
  • Redux 太多样板代码

使用 create-react-app

# Redux + Plain JS template
npx create-react-app my-app --template redux

# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript

现有项目

npm install redux react-redux @reduxjs/toolkit -D

Redux Toolkit 包括以下 API:

  • configureStore(): 包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的 slice reducer,添加你提供的任何 Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension。
  • createReducer():这使您可以为 case reducer 函数提供操作类型的查找表,而不是编写 switch 语句。此外,它自动使用该immer库让您使用普通的可变代码编写更简单的不可变更新,例如state.todos[3].completed = true.
  • createAction():为给定的动作类型字符串生成动作创建函数。该函数本身已toString()定义,因此可以使用它来代替类型常量。
  • createSlice():接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的动作创建者和动作类型。
  • createAsyncThunk: 接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分派动作类型的 thunk
  • createEntityAdapter: 生成一组可重用的 reducer 和 selector 来管理 store 中的规范化数据
  • 重新选择库中的createSelector实用程序,重新导出以方便使用。

案例

效果

iShot_2022-08-18_16.44.22.gif

counterSlice.ts

import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
import { fetchCount } from './counterAPI'


export interface CounterState {
  value: number
  status: 'idle' | 'loading' | 'failed'
}

const initialState: CounterState = {
  value: 0,
  status: 'idle'
}

export const incrementAsync = createAsyncThunk('counter/fetchCount', async (amount: number) => {
  const response = await fetchCount(amount)
  return response.data
})

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  // 这里内置了immer,可以直接操作state
  // reducers是reduces 和 actions 组合在一起的对象
  reducers: {
    increment: state => {
      state.value += 1
    },
    decrement: state => {
      state.value -= 1
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    }
  },
  //这里对异步操作incrementAsync异步状态的处理
  extraReducers: builder => {
    builder
      .addCase(incrementAsync.pending, state => {
        state.status = 'loading'
      })
      .addCase(incrementAsync.fulfilled, (state, action) => {
        state.status = 'idle'
        state.value += action.payload
      })
      .addCase(incrementAsync.rejected, state => {
        state.status = 'failed'
      })
  }
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

store.ts

import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
import logger from 'redux-logger'
import rootReducer from './rootReducer'
import { useDispatch } from 'react-redux'

import CounterSlice from '@pages/counter/counterSlice'

const middleware = getDefaultMiddleware().concat(logger)

export const store = configureStore({
  reducer: {
   counter: CounterSlice
  },
  middleware,
  devTools: process.env.NODE_ENV !== 'production'
})

export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>
export const useAppDispatch = () => useDispatch<AppDispatch>()

Counter.tsx

import React, { useState } from 'react'
import { useSelector } from 'react-redux'
import { useAppDispatch, RootState } from '@store'
import { decrement, increment, incrementByAmount, incrementAsync } from './counterSlice'

import styles from './Counter.module.less'

const Counter = () => {
  //useSelector简化redux的模板代码
  const { value } = useSelector((state: RootState) => state.counter)

  const dispatch = useAppDispatch()
  const [incrementAmount, setIncrementAmount] = useState('2')

  const incrementValue = Number(incrementAmount) || 0

  return (
    <div>
      <div className={styles.row}>
        <button className={styles.button} aria-label="Decrement value" onClick={() => dispatch(decrement())}>
          -
        </button>
        <span className={styles.value}>{value}</span>
        <button className={styles.button} aria-label="Increment value" onClick={() => dispatch(increment())}>
          +
        </button>
      </div>
      <div className={styles.row}>
        <input
          className={styles.textbox}
          aria-label="Set increment amount"
          value={incrementAmount}
          onChange={e => setIncrementAmount(e.target.value)}
        />
        <button className={styles.button} onClick={() => dispatch(incrementByAmount(incrementValue))}>
          Add Amount
        </button>
        <button className={styles.asyncButton} onClick={() => dispatch(incrementAsync(incrementValue))}>
          Add Async
        </button>
      </div>
    </div>
  )
}

export default Counter

index.tsx

import React from 'react'
import { createRoot } from 'react-dom/client'
import { HashRouter, Routes, Route } from 'react-router-dom'
import { Provider } from 'react-redux'
import { store } from '@store'
import App from './app'

import './assets/style/index.less'

const container = document.getElementById('root')
const root = createRoot(container)

root.render(
  <Provider store={store}>
    <HashRouter>
      <Routes>
        <Route key={'counter-demo'} path="/counter" element={<CounterDemo />} />
      </Routes>
    </HashRouter>
  </Provider>
)

结束语

  • 👀 目前专注于前端
  • ⚙️ 在react、react-native开发方面有丰富的经验
  • 🔭 最近在学习安卓,有自己的开源安卓项目,集成react-native热更新功能
  • ❤️ 思考、学习、编码和健身
  • 如果文章对您有帮助,三连支持一下~O(∩_∩)O谢谢!