每天对自己多问几个为什么,总是有着想象不到的收获。 一个菜鸟小白的成长之路(copyer)

前言:

​ 这篇博客主要介绍了vuex4的基本使用,从创建一个store对象,然后再store内部的属性进行一一的解释(就是vuex的五大和核心:state, getters,mutations,actions, modules),解释主要从类型,参数等等方面的解释,还是比较的详细。内容有点长,可以收藏起来,慢慢看哟(嘻嘻)


vuex介绍

​ Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

在这里插入图片描述

​ 具体就不描述了,懂得都懂,不懂的,我也说不清(哈哈哈)

vuex4安装

npm install vuex@next
//现在默认是vuex3,所以加上next安装下一个版本

//package.json文件中,查看版本
"dependencies": {
    "vuex": "^4.0.2"
  },

vuex创建一个store对象

在vuex4中提供了一个方法,createStore来创建一个store对象

如果不想看泛型分析的,可以直接跳过引用部分

createStore的类型进行分析

// vuex/types/index.d.ts
export function createStore<S>(options: StoreOptions<S>): Store<S>

分析:

  • createStore接收一个泛型, 泛型的名称为 S

  • 接收一个参数,参数为对象,对象的类型为StoreOptions

  • 返回一个Store实例

分析参数

export interface StoreOptions<S> {
  state?: S | (() => S);     //对象或则函数的形式,返回对象
  getters?: GetterTree<S, S>;
  actions?: ActionTree<S, S>;
  mutations?: MutationTree<S>;
  modules?: ModuleTree<S>;
  plugins?: Plugin<S>[];
  strict?: boolean;
  devtools?: boolean;
}

根据上面的接口,可以看出 泛型S就是 对 state的类型进行限定

分析返回值

Store 是一个泛型类

// vuex/dist/vuex.global.js
function createStore (options) {
    return new Store(options)
}

返回的就是Store类的一个实例对象

创建一个store实例对象,并且其类型定义为IRootState

import { createStore } from 'vuex'

interface IRootState {
    count: number
}

const store = createStore<IRootState>({   //定义state的类型限定
	
})

export default store 

注册到项目的APP中

import { createApp } from 'vue'
import store from '@/store'

const app = createApp(App)

app.use(store)

app.mount('#app')

这样就可以直接在各个组件中使用store了。


vuex核心模块之state

存放数据的地方(数据仓库),state可以是一个函数,也可以是一个对象

const store = createStore<IRootState>({   //定义state的类型限定
	state() {   //创建的时候,已经给state的类型进行了限定
        return {
            count: 0
        }
    }	
})

上面,我们已经定义好了一个state数据,那么我们就可以在组件中展示出来了。分类两种形式:

第一种形式:

//直接在组件中使用$store
<h1>{{$store.state.count}}</h1>

注意: 上面的这种情况有点特殊,如果是我们手动安装vuex的话,ts是对$store不认识的,就会报警告,但是不影响项目的运行流程。但是对于大多数人,肯定还是接受不了波浪线的警告。 情景大致如下:

警告

那应该如何解决呢,使ts认识$store

在vue3创建的项目中,有一个文件shims-vue.d.ts

//使用.d.ts来申明一下$store, 就可解决了
declare let $store: any

第二种形式:

借用vue3中提供的computed这个API,然后再进行展示(这里就不会存在上面的警告文件)

还需要借助一个函数 useStore, 这个是vuex内部提供的,因为在setup中是不能使用this的,就不能像vue2中通过this.$store的形式。

import { defineComponent, computed} from 'vue'
import { useStore } from 'vuex'

export default defineComponent({
  name: 'App',
  setup() { 
    const store = useStore()
    const count = computed(() => store.state.count)  //拿到count的值
    return {
      count
    }
  }
})

//在template中展示
 <h1>{{count}}</h1>  

上面两种形式的效果是一样的。(选哪种,看你随意)

  • 第一种在template中字段太长,代码不优雅
  • 第二种在需要借助两个函数,代码量增加

vuex核心模块之getters

getters的作用就是对state进行变形后,然后进行返回展示

这里的count实例不好演示,我就列举了另外的数据进行演示(更好的体现出来)

interface IRootState {
    coder: any[]
}

const store = createStore<IRootState>({
    state() {
        return {
            coder: [
                {name: '前端人员', number: 5},
                {name: '后端人员', number: 15},
                {name: '测试人员', number: 3},
                {name: '产品', number: 2}
            ]
        }
    },
    getters: {
        coderAllCount(state) {
            return state.coder.reduce((prev, item) => {
                return prev + item.number
            }, 0)
        }
    }
})

export default store

代码分析:这里的state的数据是coder,是一个数据,如果现在在界面中展示总的人数,getters就是很好的选择(其实还有一种情况就是,组件直接拿到state中的值,进行计算展示,那么想象一下,如果很多地方也需要总人数,那么就要写很多遍),在getters中直接写好,这样就可以在多处的地方使用了,而不需要重复的写相同的代码。

在组件中展示,跟state的两种形式是一样的

//形式一
<h1>{{$store.getters.coderAllCount}}</h1>
//形式二
const coderAllCount = computed(() => store.getters.coderAllCount)

getters的参数分析

在上面的实例中,已经可以看见,其中的一个参数就是state,这个state就是store中的state。分析:

ts对getters的类型分析,如果不想看,可以直接跳过

从上面可以找到,createStore接收一个参数,为对象,对象的类型为StoreOptions,其中有一个属性就是getters,定义如下

getters?: GetterTree<S, S>;  //泛型接口

//GetterTree
interface GetterTree<S, R> {  
 [key: string]: Getter<S, R>;  //泛型接口(知道方法名为字符串)
}

//Getter
type Getter<S, R> = (state: S, getters: any, rootState: R, rootGetters: any) => any;

这里考虑到了模块的存在

在上面的分析的过程中,知道了接收四个参数,分别是: state、getters、rootState、rootGetters,

当然这里考虑到了模块的存在,所以这个在下面讲解模块的时候在讨论。

这里,我们是跟Store,所以也解释了为什么GetterTree<S, S>传递了两个相同的泛型,并且这里的state跟rootState是全等的, getters和rootGetters也是全等的

个人看法: getters中的属性虽然是个函数,请把它看成是属性。(因为在使用的时候,不需要()


vuex核心模块之mutations

1、mutations是干什么的?

mutations是更改vuex中的store的状态唯一方法。

2、怎么触发mutations中的方法?

通过createStore创建出来的实例对象中的commit属性,该属性是一个方法,接收的参数可以是字符串和对象的形式(payload:提交载荷

3、mutations中的函数可以是异步函数吗?

不可以,mutation中的必须是同步代码(重要的原则)。比如说:在mutation的日志的时候,devtools 都需要捕捉到前一状态和后一状态的快照,然而当时异步函数的时候,devtools根本不知道什么执行,从而也没法捕捉,那么状态就是一个可不追踪状态

参数分析:

ts 对 mutations 的参数类型分析,如果不想看,可以直接跳过

从上面可以找到,createStore接收一个参数,为对象,对象的类型为StoreOptions,其中有一个属性就是mutations,定义如下:

mutations?: MutationTree<S>;    //泛型接口

export interface MutationTree<S> {
 [key: string]: Mutation<S>;   //泛型接口(知道方法名为字符串)
}

//Mutation
export type Mutation<S> = (state: S, payload?: any) => any;

从上面的类型分析,我们可以看出 mutation中的方法接收两个参数state 、 payload(可选)

mutation只能修改当前模块中的state

代码示例

// store/index.ts
interface IRootState {
    count: number
}

const store = createStore<IRootState>({
    state() {
        return {
            count: 0
        }
    },
    mutations: {
        increment(state) {  //这里的state的类型可以自动推导为IRootState
            state.count += 1
        }
    }
})

在组件中触发(例如:点击事件)

//组件中
import { useStore } from 'vuex'
export default defineComponent({
  name: 'App',
  setup() { 
    const store = useStore()
    const btn = () => {
        store.commit('increment')  // increment对应的就是mutations中的方法名
    }
    return {
      btn
    }
  }
})

在上面,唯一容易出错的感觉的单词容易写错,,然后产出bug。如果想解决上面的问题,那就使用:

常量代替Mutation事件

//mutation-types.js
export const INCREMENT = 'INCREMENT'    // 加
// store/index.ts
import { INCREMENT } from './mutation-types'

mutations: {
    [INCREMENT] (state) {
		state += 1
    }
}
//组件中
import { INCREMENT } from '@/store/mutation-types'

store.commit(INCREMENT)  //使用导出的常量

这样,就可以减少错误,尽管单词写错了。而且这样还有个好处就是,可以一眼分析出:执行了哪些mutations,而不会不停的mutations中, 折叠寻找代码。

提交载荷(payload)

这里主要是两种形式: 字符串对象

推荐使用:对象形式

代码演示:

const store = createStore<IRootState>({
    state() {
        return {
            count: 0
        }
    },
    mutations: {
        increment(state, payload) {  //这里的state的类型可以自动推导为IRootState
            state.count += payload.count
        }
    }
})
//字符串形式
const btn = () => {
    store.commit('increment', {count: 10})
    //或则
    store.commit(INCREMENT, {count: 10})
}
//对象的形式
const btn = () => {
    store.commit({
        type: 'increment',
        count: 10
    })
    //或则
    store.commit({
        type: INCREMENT,
        count: 10
    })
}

//这里对象的形式: payload是指向传递的整个对象

vuex核心模块之actions

1、action可以直接修改state的状态吗?

不可以,store中修改state的值,只有mutation,所以action提交的是mutation,让mutation来进行修改

2、怎么触发actions中的方法?

通过createStore创建出来的实例对象中的dispatch属性,该属性是一个方法,接收的参数可以是字符串和对象的形式(payload:提交载荷

3、action中可以写异步函数吗?

可以,action就是专门用来处理异步的,当异步执行完成之后,触发mutation,从而修改state的状态,让state变成可追踪的。

参数分析

ts 对 action的参数类型分析,如果不想看,可以直接跳过(推荐看下,因为这个参数比较复杂)

从上面可以找到,createStore接收一个参数,为对象,对象的类型为StoreOptions,其中有一个属性就是actions,类型定义如下:

actions?: ActionTree<S, S>;

export interface ActionTree<S, R> {
 [key: string]: Action<S, R>;
}

export type Action<S, R> = ActionHandler<S, R> | ActionObject<S, R>;
//联合类型  ActionHandler | ActionObject

export type ActionHandler<S, R> = (this: Store<R>, injectee: ActionContext<S, R>, payload?: any) => any;
//第一个参数为this (不用传递)
//第二个参数 注入一个context, 为一个对象
//第三个参数 提交负载(payload)

export interface ActionObject<S, R> {   //针对于模块中的action,触发根节点中的store
 root?: boolean;
 handler: ActionHandler<S, R>;
}

//注册context的类型
export interface ActionContext<S, R> {
 dispatch: Dispatch;
 commit: Commit;
 state: S;
 getters: any;
 rootState: R;
 rootGetters: any;
}

从上面的类型大致,可以分析出:

actions中的方法,一般接收两个参数第二个参数为可选参数

参数一: context(必选参数)

context是一个对象,包含以下属性:commit, dispatch, state, rootState, getters, rootGetters

  • commit:触发mutations中的方法,修改state
  • dispatch: 触发actions中的方法
  • state: 拿到当前模块中的state中的值
  • getters: 拿到当前模块中的getters中的值
  • rootState: 拿到根节点中的state值
  • rootGetters: 拿到根节点中的getters中的值
//参数一的两种形式的写法
actions: {
    //写法一: 直接写context对象
    increment(context) {},
    //写法二: 解构对象,只列出我们所需要的属性名
    decrement({commit, dispatch})
}

参数二: payload(可选参数)

payload的存在在于,我们是否给action分发的是否,是否携带参数。

// store/index.ts
const store = createStore<IRootState>({
    state() {
        return {
            count: 0
        }
    },
    mutations: {
        increment(state, payload) {  //这里的state的类型可以自动推导为IRootState
            state.count += payload.count
        }
    },
    actions: {
        increment({ commit }, payload) {
            setTimeout(() => {
                commit({
                    type: 'increment',
                    count: payload.count
                })
            }, 1000)
        }
    }
})

情况一: 没有携带参数

const store = useStore()

cosnt btn = () => {
    store.dispatch('increment')   // 在这里就没有携带参数,所以payload是不存在的
}

情况二: 携带参数

const store = useStore()

//两种形式: 直接传递参数形式 和  对象的形式
cosnt btn = () => {
    store.dispatch('increment', {count: 100})   // 在这里就有携带参数,所以payload是就是一个对象
}  
//payload   ==>  {count: 100}

cosnt btn = () => {
    store.dispatch({
        type: 'increment',
        count: 100
    }) 
}
//payload  ==> {type: 'increment', count: 100}   对象的形式有点差异

vuex核心模块之module

1、为什么要使用module?

​ 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。所以,就需要对模块进行划分,这样思路也比较清晰,也利于团队分工开发。

类型分析:

ts对模块类型的进行分析,如果不想看,可以直接跳过

定义一个模块:

const countModule = {}

那么countModule的类型是什么呢?

//这是vuex4对模块的类型定义
export interface Module<S, R> {
 namespaced?: boolean;
 state?: S | (() => S);
 getters?: GetterTree<S, R>;
 actions?: ActionTree<S, R>;
 mutations?: MutationTree<S>;
 modules?: ModuleTree<R>;
}

从上面可以看出,跟createStore创建的基本实例是一样的,就是多了一个namespaced这属性(后面会说到)

所以定义一个模块的类型确定

import { Module } from 'vuex'
const countModule: Module<S, R> = {}

S: 是模块中state的类型限定

R: 是根store中的state的类型限定

基本使用:(计数模块为例)

准备工作:接口定义

interface ICountState {    //count模块中state对象限定
    count: number
}
interface IRootState {     //根store中state的类型限定
    count: number
}

第一步:定义一个countModule模块

import { Module } from 'vuex'

//type: Module<S, R>
//接收两个泛型,第一个S 为当前模块的state的类型, 第二个R: 就是跟节点的state类型
const countModule: Module<ICountState, IRootState> = {
    state() {
        return {
            count: 0
        }
    },
    getters: {},
    mutations: {
        increment(state) {
            console.log('模块store中的increment方法')
            state.count += 1
        }
    },
    actions: {}
}
export default countModule

第二步:在根store中注册计数模块

import { createStore } from 'vuex'
import countModule from './count/count'
const store = createStore<IRootState>({
    state() {
        return {
            count: 0
        }
    },
    mutations: {
        increment(state) {
            console.log('根store中的increment方法')
            state.count += 1 
        }
    }
    modules: {
        countModule
    }
})

代码分析:

定义了一个计数模块,该模块中的state中有一个count属性,mutations中有个increment方法;根store中state也有个count属性,mutations中有个increment方法;(这里只测试mutations,因为getters和actions跟mutations的情况是一样的)

正题开始:

1、根store中的state和模块中的state的区分

疑问:怎么渲染根store中的count计数模块中的count

<div>{{$store.state.rootCount}}</div>    //渲染根store (这个应该没有疑问吧)
<div>{{$store.state.countModule.count}}</div>  //正确渲染count模块中的count (有疑问吗?)

//为什么不是下面的写法呢?
<div>{{$store.countModule.state.count}}</div>  //消耗的性能更大,源码的内部数据结构不是这样设计

解答:state中的合并操作

vuex中合并操作的数据结构:

//简单大致如下:
const store = createStore({
    state() {
        return {
            rootCount: 0,
            countModule: {      //这里直接添加module的state
                count: 1
            }
        }
    }
})
2、根store中的mutation和模块中的mutation的区分

疑问: mutations中的疑惑

  • 如何根store中的mutations?
  • 如何使用模块中的mutations?
  • 如果根store中的方法与模块中的mutations中的方法一致,是怎么处理的
测试案例:
import { useStore } from 'vuex'

export default defineComponent({
    setup() {
      const store = useStore()
      const btn = () => {
          store.commit('increment')
      }
    }
  })

我们通过store中commit方法来触发了mutations中的increment,但是由于根store中内部mutations中有increment方法,而模块中的mutations中也有increments方法,所以当点击会发生的效果呢?

初始状态的现象:

在这里插入图片描述

当点击按钮之后的效果

在这里插入图片描述

通过上面的测试,我们知道,commit中同一个名字的increment都会被触发,无论是模块中的还是根store中的。

那么根store中的mutation和模块中的mutation是怎么合并呢?

//会集中到根store中的mutations中
const store = createStore({
    mutations: {
        increment(){},   //根store
        increment(){},   //模块中
    }
})

比如像上面的实例代码(我们可以想象是这样理解)。

getters和actions的效果是一样的。

就想修改触发模块中的increment方法,而不想触发根store中的increment;或则说,就想触发根store中的increment方法,而不想触发模块中的increment,那么我们应该怎么处理呢?

在vuex4中,想要处理上面的类似效果,就需要使用命名空间

命名空间的使用

在分析创建模块的类型的时候,里面有个属性namespaced,为一个boolean值,就是提醒我们是否需要开启命名空间。

  • 默认情况下,模块内的action额mutations仍然是注册在全局的命名空间中的:
    • 这样使得多个模块能够对同一个action或mutation作出响应
    • getters同样也默认注册在全局命名空间
  • 如果我们希望模块具有跟高的封装度和复用性,可以添加namespaced: true的方式使其成为待命名空间的模块:
    • 当模块被注册之后,它的所有getters,actions和mutation都会自动根据模块注册的路径调整命名。

对模块中的各个属性方法的访问:

//state (当然原来也是这么访问的)
store.state.countModule.count

//getters
store.getters['countModule/coderAllCount']

//mutations
store.commit('countModule/increment')

//actions
store.dispatch('countModule/incrementAction')

模块内部触发根store中的mutation

有不有 这种情况, 模块内部的action异步拿到数据后,可以想修改根store中的mutation呢? 应该会存在这种情况吧。那么这种情况该怎么处理呢?

在上面分析action的类型的时候,有一个类型是专门针对于模块的

export interface ActionObject<S, R> {   //针对于模块中的action,触发根节点中的store
  root?: boolean;
  handler: ActionHandler<S, R>;
}

这里有个root属性,就是用来告诉是否用来修改根store中的mutation

actions: {
    incrementAction({commit}, payload) {
        setTimeout(() => {
            //默认情况下,就是触发模块内部mutation中的increment
            commit('increment', payload)
            //加上root:true, 那么这时候触发的就是根store中的increment了
            commit('increment', payload, {root: true})
        }, 1000)
    }
}

辅助函数

当然还可以在vuex使用辅助函数,可以看我的另外一篇博客哟,在vue3中使用,对辅助函数的封装

copyer_xyf: vue3中使用辅助函数


总结:

​ vuex的基本用法大致就是这样吧,跟vuex3的区别也不是很大,以前学了vue2, 还是可以很容易的接收其中的呢。

​ 喜欢的点,点个赞呗!

Logo

前往低代码交流专区

更多推荐