• react+ts实战之 @reduxjs/toolkit


    前言

    Store的宗旨是为了复杂组件数据共享而设计,因此使用场景在设计层尽可能减少,否则极大加剧了项目的复杂度,正如官方所说的那样在你考虑用和不用的时候,那就是不用。

    react的社区是足够大的,他不像vue那样cli官方给你构建一套完整的全家桶,如vue+vue-router+vuex,它更多的是像买车一样,给你提供选配的模式,出了本身的基础配置之外,你是要定制的皮制座椅(flux)还是星空顶(redux)…需要什么样的插件,自己就去市场选配什么插件,我们今天来讲一下主流的redux

    一、redux的工作流

    View调用store.dispatch发起Action->store接受Action(action传入reducer函数,reducer函数返回一个新的state)->通知store.subscribe订阅的重新渲染函数
    在这里插入图片描述

    二、redux曾经的使用案例

    在前几年我们使用的就是这样,没错,非常完整规范的redux操作流程。reduceractionstore清晰明了,就是有一点,em…老太婆裹脚—又臭又长。所以在vue2发布以后,我们团队果断选择了vue直到现在,我个人目前也更为擅长vue的各个版本。
    store.js

    import {createStore} from 'redux'
    import Reducer from './reducer'
    const Store = createStore(Reducer)
    
    export default Store; 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Reducer.js

    const Reducer = (state,action)=>{
    	if(typeof state === 'undefined'){
    		return []
    	}
    	switch (action.type){
    		case 'CON_TODO':
    		return [...state,action.text]
    		break;
    		case 'TONN_DEL':
    		state.splice(action.text,1)
    		return state
    		break;
    		default:
    		return state
    			break;
    	}
    }
    
    
    export default Reducer;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    action.js

    var Action = (text)=>{
    	return{
    		type:"CON_TODO",
    		text:text
    	}
    }
    export default Action; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    实际使用

    import React from 'react'
    import ReactDOM from 'react-dom'
    import {createStore} from 'redux'
    import Action from './Store/action.js'
    import Store from './Store/store.js'
    class App extends React.Component{
    	constructor(props){
    		super(props)
    		this.shuju = this.shuju.bind(this)
    		this.state={
    			arr:Store.getState()
    		}
    	}
    	del(a){
    		Store.dispatch(Action1(a))
    	}
    	componentDidMount(){
    		Store.subscribe(this.shuju)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    三、@reduxjs/toolkit 基于redux的工具库

    3.1 基础案例

    @reduxjs/toolkit这个是我们react实战项目中的一个重点知识,能帮我们解决redux多余的代码,而且让我们的代码更加清晰明了。以下是一个简单的案例,我们引入了一个关于counter的reducer切片

    // store/index.ts
    import { configureStore } from '@reduxjs/toolkit'
    import counterSlice from './counterSlice'
    export const store = configureStore({
      reducer: {
        counter: counterSlice
      },
    })
    // Infer the `RootState` and `AppDispatch` types from the store itself
    export type RootState = ReturnType<typeof store.getState>
    // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
    export type AppDispatch = typeof store.dispatch
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    再来看一下具体的切片内容:

    // store/counterSlice.ts	
    import { createSlice, PayloadAction, createAsyncThunk } from '@reduxjs/toolkit'
    export interface CounterState {
      isLogin: boolean
      value: number
      featchVal: number
    }
    
    const initialState: CounterState = {
      isLogin: false,
      value: 0,
      featchVal: 0
    }
    
    export const counterSlice = createSlice({
      name: 'counter',
      initialState,
      reducers: {
        increment: (state) => {
          state.value += 1
        },
        decrement: (state) => {
          state.value -= 1
        },
        incrementByAmount: (state, action: PayloadAction<number>) => {
          state.value += action.payload
        }
      }
    })
    
    // Action creators are generated for each case reducer function
    export const { increment, decrement, incrementByAmount } = counterSlice.actions
    
    export default counterSlice.reducer
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    3.2 @reduxjs/toolkit+store持久化

    我们如果想加上store的持久化呢,也就是说对store的内容进行本地存储,这里使用的是redux-persist,此时注意了,@reduxjs/toolkit仍然支持,也就是说项目基本不需要引入redux的任何依赖,它本身就是对redux的功能的封装与补充,而且支持自定义的中间件middleware,再来看一下:

    // store/index.ts
    import { configureStore, combineReducers } from '@reduxjs/toolkit'
    import { persistStore,persistReducer} from 'redux-persist'
    import storage from 'redux-persist/lib/storage'
    import counterReducer from './counterSlice'
    // ...
    
    export const rootReducer = combineReducers({
      counter: counterReducer
    })
    const persistConfig = {
      key: 'root',
      storage ,
      blacklist:[]
    }
    const myPersistReducer = persistReducer(persistConfig, rootReducer)
    export const store = configureStore({
      reducer: myPersistReducer,
      middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware({
          serializableCheck: false,
        })
    })
    
    export const persistor = persistStore(store)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    3.3 处理异步

    通常情况下store是没有处理异步的能力的,所以我们更多的是通过市场上提供的一些处理异步的解决方案,如redux-thunk
    但是我们这里不需要,因为仍然有处理异步的方案,我们接着在counterSlice.ts中进行拓展

    import { createSlice, PayloadAction, createAsyncThunk } from '@reduxjs/toolkit'
    
    export interface CounterState {
      isLogin: boolean
      value: number
      featchVal: number
    }
    
    const initialState: CounterState = {
      isLogin: false,
      value: 0,
      featchVal: 0
    }
    
    export const fetchSettimeout = createAsyncThunk(
      'counter/fetchSettimeout', (id: number)=>{
        return new Promise<number>((resolve) => {
          setTimeout(()=>{
           resolve(id)
          },3000)
        })
      }
    )
    
    export const counterSlice = createSlice({
      name: 'counter',
      initialState,
      reducers: {
        increment: (state) => {
          state.value += 1
        },
        decrement: (state) => {
          state.value -= 1
        },
        incrementByAmount: (state, action: PayloadAction<number>) => {
          state.value += action.payload
        }
      },
      extraReducers: (builder) => {
        builder.addCase(fetchSettimeout.fulfilled, (state, action) => {
          console.log(state, action)
          state.featchVal = action.payload
        })
      }
    })
    // Action creators are generated for each case reducer function
    export const { increment, decrement, incrementByAmount } = counterSlice.actions
    export default counterSlice.reducer
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    我们用 fetchSettimeout方法封装了一个setTimeout来模拟了异步,这里只是一种写法,更多的查看点击前往官网

    3.4 Hook封装

    项目中我们希望借助react-redux来实现store的管理,但是不能直接去使用其中的一些api,可能会导致ts错误,因此我们单独拆出来做了一层Hook封装并进行类型声明

    // src/hooks
    import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
    import { store } from '@/store'
    
    
    export type RootState = ReturnType<typeof store.getState>
    export type AppDispatch = typeof store.dispatch
    export const useAppDispatch = () => useDispatch<AppDispatch>()
    export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    3.5 实际使用案例
    root.render(
      <React.StrictMode>
        <Provider store={ store }>
          <App />
         </Provider>
       </React.StrictMode>
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    import { RootState,useAppDispatch,useAppSelector } from '@/hooks/store'
    import { increment, decrement, fetchSettimeout } from '@/store/counterSlice'
    const Home: React.FC = () => {
      const dispatch = useAppDispatch()
      const {featchVal, value} = useAppSelector((state: RootState) => state.counter)
      return (
        <div className="home">
           数据变化:{featchVal}
           数据:{value}
          <Button type="primary" onClick={() => dispatch(increment())}>store点击++</Button>
          <Button type="primary" onClick={() => dispatch(fetchSettimeout(21313213))}>点击请求</Button>
        </div>
    }
    export default Home
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    总结与对比

    不知道大家看出来什么没有,有没有跟vuex的语法特别接近了呢,正所谓所谓合久必分,分久必合。任何一个框架除了性能作为首选推荐之外,其次就要考虑哪种语法更贴合开发的使用习惯,更简单明了。不知道当前的你是不是还在使用最原始的redux+local来管理数据呢???

    vuex@reduxjs/toolkit
    namename
    stateinitialState
    mutationscreateSlice-reducers
    actionscreateAsyncThunk+extraReducers
    modulesconfigureStore-reducer
  • 相关阅读:
    探索原味BFF模式
    [elementuiPlus]el-select改变时触发el-input校验的解决方法
    LabVIEW连接PostgreSql
    Flutter学习笔记 --单一子元素组件
    kafka集群与redis集群的概念对应关系
    “基础 - 中级 - 高级”Java 后端 25 个技术栈面试题集结
    一文理解深度学习框架中的InstanceNorm
    智云通CRM:CRM数据库在经营客户中有什么作用?
    路由进阶:route-policy实验配置
    (02)Cartographer源码无死角解析-(15) Node::AddTrajectory()→回调函数之数据流向分析
  • 原文地址:https://blog.csdn.net/qq_40513881/article/details/125601749