手把手教你怎样在vue3中使用redux

762 阅读4分钟

前言

现在的web应用基本上都走上了数据驱动的道路,而数据状态的管理则理所当然成为了项目开发的核心,所以理论上只要统一状态层,view渲染可以随意更换框架和模式。

而我们在做项目的时候大部分都是vue用vuex。react用redux。以至于很多同学都以为所有的状态管理容器是和页面渲染框架一一绑定的,所以突然想实验一下用redux做状态管理,而vue只做一个简简单单的页面渲染器 在这里插入图片描述

思路

我们知道redux的state 只是一个普通的js对象,是无法感知视图的。redux的整套流程和vue的渲染驱动机制其实是相互独立的。不像vuex本身就是一个vue实例,state属性本身就是reactive的,声明在template里,属性的set的方法会自动收集依赖,也就是vue本身的那套双绑模式。所以vuex不需要做任何操作天然的支持vue,或者说vuex就是一个实现了flux思想的全局vue。

在这里插入图片描述 所以就像官网说的,我们需要做一些简单绑定让redux接入到vue的数据渲染模式中,

然而这个网址已经失效。。。。 ̄□ ̄||

不过redux官网也有通用的ui驱动方案

在这里插入图片描述

大致思路就是在我们dispatch action 之后的回调中对ui层进行更新。不过如果每次state发生变化都对vue进行$forceUpdate不免太僵硬且浪费性能了。

参照react-redux将当前组件需要的参数从redux的state中接管为react组件的状态(props),让组件本身的数据代为驱动ui的更新。

而且vue本身的data就是reactive的,所以只需要将redux中的state注入到vue的data中,在redux的subscribe回调中根据state对data进行修改就可以顺利的驱动vue进行页面渲染。 所以只需要在vue组件挂载之前将state注入到data中即可,如果是vue2,我们只能用mixin将数据混入进组件,不过在vue3中我们则可以用composition component 在setup中进行注入

示例 描述:做一个简单的小功能进行测试,点击sider内页面标签将页面名分发到state中,再驱动header页面更新。

效果: 在这里插入图片描述

步骤:

1 构建redux store

import { createSlice, PayloadAction } from '@reduxjs/toolkit'
export interface GlobalState {
    currentRoute: string,
}
// 初始化state
const initialState: GlobalState ={
    currentRoute: '应用程序管理',
}
export const globalSlice = createSlice({
  name: 'global',
  initialState,
  reducers: {
    updata: (state, action: PayloadAction<string>) => {
      state.currentRoute = action.payload
    },
  },
})

// Action creators are generated for each case reducer function
export const { updata } = globalSlice.actions

export default globalSlice.reducer




import { configureStore } from '@reduxjs/toolkit'
import global from './reducers/globalSlice'
export const store = configureStore({
    reducer: {
        global:global
    },
  })
  store.subscribe(()=>{
      console.log('subscribe')
  })

此时已经生成的state,以及对应的action

在这里插入图片描述

2 建立redux与vue的连接组件reduxConnect

注意: redux 为了保证单选数据流,所以state对象中的属性的writable都是false,也就是不可以直接对state进行修改,如果我们直接将redux的state换算到vue data中 ,又因为vue是通过set方法去触发依赖的。所以就直接破坏了vue的数据驱动模式。所以要将state深拷贝后再换算进vue

import {  reactive,onMounted } from 'vue';
import { store } from '../config/store/store.ts';

// 深拷贝
export const clone = (target: any) => {
    if (typeof target === 'object') {
        const cloneTarget: any = Array.isArray(target) ? [] : {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
};

/* 
mapStateToData:state描述集合,示例:[{global:['currentRoute']}]
*/
export default (mapStateToData:Array<any>)=>{
    interface ObjType {
        [propName: string]: any;
    }
    const state:ObjType = reactive({}) 
    // 通过redux state 生成 vue data
    const generate = ()=>{
        if(!mapStateToData.length)return
        const _state = store.getState() 
        for(const map of mapStateToData){
            const k = Object.keys(map)[0]
            const module = map[k]
            for(const v of module){
                 // 浅比较替换
                  if(!state[v]||state[v]!=_state[k][v]){
                     if(typeof _state[k][v] ==='object'){
                        state[v] =clone(_state[k][v]) 
                    }else{
                        state[v] = _state[k][v]
                    }
            }
        }
    }
    generate()
    onMounted(()=>{
        // 在state完成更新的回调中更新vue data 驱动页面更新
        store.subscribe((v:any)=>{
            generate()
        })
    })
    return {state}
}

3 在header组件中根据state的描述规则连接vue和redux

   setup(props){
        return {...reduxConnect([{global:['currentRoute']}])}
    },

此时state已经注入进了vue组件(其实不如说是根据state生成新的vue data属性更贴切) 在这里插入图片描述 4 在sider组件内dispatch action 对state进行更新,同时驱动页面更新

this.store.dispatch(updata(params.label))

后记 这次的一次小实验主要是验证一些自己的小猜想,顺便巩固一下相关的知识和理解。无论是vuex还是redux都是很优秀的框架。关于状态容器,用法也有很多种。有的同学从来不用只用组件内本身的状态进行管理,有的全局挂一个对象进行管理,有的则所有的状态都托付于容器进行管理。不能说谁对谁错,项目环境千差万别没有最好的用法,只有最适合的用法