最近一个月发版好几次,都没时间摸鱼了,所以乘着发版空挡期,整理些常用的基础知识,方便更好的ctrl c v,增加摸鱼时间。(⊙o⊙) 开卷吧~~~
一、vuex 的 4 个模块
一般多个视图依赖于同一个状态数据, 或者层级比较深时, 可以考虑用 vuex, 基本使用如下:
- 安装 vuex 依赖包:
npm install vuex --save
- 导入 vuex 包
// store/index.js
import Vuex from 'vuex'
vue.use(Vuex)
- 创建 store 对象
// store/index.js
...
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
// 如果是需要每次新生成一个store, 可以写成function的形式
export default () => {
return new Vuex.Store({
state,
mutations,
actions,
getters
})
}
- 将 store 对象挂载到 vue 实例中
// main.js
import store from '@/store/index'
new vue({
el: 'app',
router,
store, //将创建的共享数据对象,挂载到vue实例中,所有的组件,就可以直接从store中获取全局的数据了
render: (h) => h(app),
})
接下来看下vuex的4个模块的使用,附上官方的vuex流程图:
1.state
它是 vuex 管理的状态对象(唯一的), 类似页面的data。
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 包含n个状态数据的对象, 相当于data
const state = {
count: 0, // 初始值
name: '',
token: '',
}
页面使用 state 数据:
// xxx.vue
<template>
<div>{{count}}</div>
</template>
<script>
export default {
computed:{
count(){
return this.$store.state.count
}
}
}
</script>
<style>
</style>
2.mutations
包含多个用于直接更新状态数据(state)的方法的对象, 不能包含异步代码。另外 mutation 只能有连个参数, 第一个是 state, 第二个是传入的数据, 如果有多个参数需要传递, 需要把参数封装成对象传递(subscribe通过mutation.payload获取传入mutation的第二个参数)。
// store/index.js
const mutations = {
// +data
INCREMENT(state, data) {
state.count = state.count + data
},
// -1
DECREMENT(state) {
state.count--
},
}
页面通过 this.$store.commit('mutation 名称')触发 mutation, 或者通过 action 中的 commit('mutation 名称')触发。
// xxx.vue
...
mounted() {
this.$store.commit('INCREMENT', 1);
}
// 虽然可以直接在页面通过this.$store.state.count = val,直接修改state中的数据,但是vue不推荐这么做,修改state里面的数据还是需要通过commit触发。
// 设置vuex的strict:true可以限制不能通过this.$store.state.count = val直接修改state, 一般在开发环境加入改限制。
const isDev = process.env.NODE_ENV === "development";
export default new Vuex.Store({
strict: isDev ? true : false,
state,
mutations,
actions,
getters
})
...
3.actions
包含多个用于间接更新状态数据的方法的对象, 通过执行 commit('mutation 名称')来触发 mutation 的调用, 从而更新状态数据 state,。
// store/index.js
const actions = {
decrement({ commit }) {
commit('DECREMENT')
},
updateCount({ commit, state }, data) {
if (state.count === 10) {
commit('INCREMENT', data)
}
},
updateCountAsync({ commit }, data) {
setTimeout(() => {
commit('INCREMENT', data)
}, 1000)
},
}
通过组件中的 this.$store.dispatch('mutation 名称', newData)触发
// xxx.vue
...
methods:{
decrement(){
this.$store.dispatch('decrement')
},
updateCount(){
this.$store.dispatch('updateCount', 1)
},
incrementAsync(){
this.$store.dispatch('incrementAsync', 1)
},
}
...
4.getters
包含多个计算属性 getter 方法的对象,类似computed。
// store/index.js
const getters = {
comName(state) {
return `${state.name}:${state.count}`
},
}
页面中通过 this.$store.getters.xxx 获取 getter 值。
// xxx.vue
...
computed: {
comName () {
return this.$store.getters.comName
}
},
...
二、mapState、mapGatter、mapMutation 和 mapAction
使用mapState、mapGatter、mapMutation 和 mapAction可以简化上面的写法:
// xxx.vue
import { mapState, mapGetters, mapMutation, mapAction } from 'vuex'
computed:{
...mapState(['count']),
...mapGetters(['comName'])
/*相当于
count(){
return this.$store.state.count
},
comName(){
return this.$store.getters.comName
}
如果数据名字不一致,参数需要用对象表示
...mapState({
count2: 'count'
// 或者用方法形式
// count2: (state) => state.count
})*/
}
methods: {
...mapMutation(['INCREMENT']),
// 也可以写成对象的形式
// ...mapMutations({ increment: 'INCREMENT' })
...mapAction(['decrement'])
}
mounted() {
this['INCREMENT'](1);
// 相当于
// this.$store.commit('INCREMENT', 1);
this.decrement()
// 相当于
// this.$store.dispatch('decrement')
}
三、vuex的module模块
当模块比较多,比较复杂可以考虑使用vuex的module模块。默认情况下vuex会把数据和方法放在全局命名空间,如果不加模块名多个模块包含相同的方法名,调用的时候就会访问所有模块中的属性(即如果没有加namesapced:true, 页面是可以直接通过...mapAction(['setBreadcrumbList']); this.setBreadcrumbList(1);等触发模块的方法。),如果多个模块含有相同名称, 就会有问题。所以一般我们会开启模块命名空间:namesapced:true。当模块被注册后,它的所有getter、action及mutation都会自动根据模块注册的路径调整命名,也就是说,我们在调用这些方法时,需要加上这个文件的路径(比如我要访问pageset这个文件中的state里边的某个属性:this.$store.state.pageset。后边这个pageset就是多了个pageset.js模块名),相当于独立的区块去使用,模块和模块之间互不干扰。
// 1.store/index.js
import pageset from './pageset'
export default new Vuex.Store({
state,
mutations,
actions,
getters,
// 模块化部分
modules: {
pageset,
},
})
// 2.store/pageset/index.js
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
export default {
namespaced: true, //开启命名空间,保证模块跟模块是独立的,这样不同的模块可以有相同的mutations或者actions方法名。
state,
getters,
mutations,
actions,
}
// 3.store/pageset/state.js
export default {
userDetailInfo: {}, //用户详细信息
breadcrumbList: [], // 面包屑导航列表
}
// 4.store/pageset/mutations.js
export const SET_USER_DETAIL_INFO = 'SET_USER_DETAIL_INFO' //设置用户详细信息
export const SET_BREADCRUMB_LIST = 'SET_BREADCRUMB_LIST' // 设置面包屑导航列表
export const CLEAR_BREADCRUMB_LIST = 'CLEAR_BREADCRUMB_LIST' // 清空面包屑导航列表
// 5.store/pageset/mutations.js
import * as types from './mutation-types'
export default {
[types.SET_USER_DETAIL_INFO](state, data) {
// state为pageset模块内部的state
state.userDetailInfo = data
},
[types.SET_USER_DETAIL_INFO](state, { key, value }) {
state.userDetailInfo[key] = value
},
[types.SET_BREADCRUMB_LIST](state, list) {
state.breadcrumbList = [...list]
},
[types.CLEAR_BREADCRUMB_LIST](state) {
state.breadcrumbList = []
},
}
// 6.store/pageset/actions.js
import * as types from './mutation-types'
export default {
setUserDetailInfo({ commit }, params) {
commit(types.SET_USER_DETAIL_INFO, params)
},
setUserInfoByKey({ commit }, params) {
commit(types.SET_USER_DETAIL_INFO, params)
},
setBreadcrumbList({ commit }, params) {
commit(types.SET_BREADCRUMB_LIST, params)
},
clearBreadcrumbList({ commit }) {
commit(types.CLEAR_BREADCRUMB_LIST)
},
}
// 7.store/pageset/getters.js
export default {
userInfo: (state) => state.userDetailInfo,
}
页面获取/修改数据:
mounted() {
// 获取面包屑导航数据state
console.log(this.$store.state.pageset.breadcrumbList)
//获取getters数据
console.log(this.$store.getters['pageset/userInfo'])
// 触发mutataion
this.$store.commit('pageset/SET_BREADCRUMB_LIST', list)
// 清空数据action
this.$store.dispatch('pageset/clearBreadcrumbList');
// 设置
this.$store.dispatch('pageset/setBreadcrumbList', list);
},
使用 mapState、mapGatter、mapMutation 和 mapAction 的写法:
// xxx.vue
import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'
computed: {
...mapState({
breadcrumbList: state => state.pageset.breadcrumbList
})
...mapGetters({
userInfo: getter => getter.pageset.userInfo
// userInfo: 'pageset/userInfo'
})
// 或者用数组写法(简写)
// ...mapState('pageset', ['userDetailInfo','breadcrumbList'])
// ...mapGetters('pageset', ['userInfo'])
// 或者
// ...mapGetters(['pageset/userInfo'])
},
methods:{
...mapMutaions('pageset', ['SET_BREADCRUMB_LIST']),
// 或者
// ...mapMutations(['pageset/SET_BREADCRUMB_LIST']),
...mapActions('pageset', ['setBreadcrumbList', 'clearBreadcrumbList']),
}
mounted() {
// 触发mutataion
this[SET_BREADCRUMB_LIST](list);
// 或者
// this['pageset/SET_BREADCRUMB_LIST']();
// 清空数据action
this.clearBreadcrumbList();
// 设置
this.setBreadcrumbList(list);
},
但是如果数据比较多,或者这个模块下命名空间比较深入,还有其他模块,嵌套层级比较深就会变成下面这样子:
...mapState({
breadcrumbList: state => state.pageset.xxx.breadcrumbList,
a: state => state.pageset.xxx.a,
b: state => state.pageset.xxx.b,
c: state => state.pageset.xxx.c,
d: state => state.pageset.xxx.d,
})
methods: {
...mapMutations(['pageset/xxx/SET_BREADCRUMB_LIST']),
setInfo () {
this['pageset/xxx/SET_BREADCRUMB_LIST'] ();
}
}
会有很多重复部分,我们可以在 vue 文件中引入 createNamespacedHelpers,然后将模块名 pageset 作为参数,传进来。它会创建基于某个命名空间(pageset)辅助函数,并返回一个对象,对象里有新的绑定在 pageset 上的辅助函数。
// 引入createNamespacedHelpers
import { createNamespacedHelpers } from 'vuex';
// 定义mapState、mapMutations,并将模块名pageset传入createNamespacedHelpers
const { mapState, mapMutations } = createNamespacedHelpers('pageset');
computed: {
...mapState({
breadcrumbList: state => state.breadcrumbList,
a: state => state.a,
})
},
methods: {
...mapMutations(['SET_BREADCRUMB_LIST']),
},
以上触发的是pageset模块自身的mutations和action来修改模块自身的state,当然vuex也可以在模块内部获取全局的state,触发全局的mutations和action,例如下面在a 模块调用全局 store:
// store/a/getters.js
import * as types from './mutation-types'
export default {
count2 (state, getters, roortState) {
// state是当前a模块的state, rootState是全局命名空间的state。
return state.
}
}
// store/a/action.js
export default {
getXXX({commit}, data) {
// 触发全局的mutation, 传入全局的state
commit('INCREMENT', rootState.count, { root: true });
},
getYYY(ctx, data) {
let {commit, dispatch, state, rootState, rootGetters} = ctx;
// 触发全局的action, 传入全局的state
dispatch('updateCount', rootState.count, { root: true });
}
}
同样多个模块之间也可以互相调用。例如b 模块调用 a 模块的mutations或者action,需要给mutations/action传递第三个参数{root: true}。action函数中第一个参数是context,其包含:{commit, dispatch, state, roootState, rootGetters}, 而commit/dispatch又包含三个参数:{mutationName, data, {root: true}},第一个参数是异步提交的mutation名,第二个参数是传进来的参数,第三个是配置选项,声明不是执行当前模块。
// store/a/action.js
import * as types from './mutation-types'
export default {
getApp({commit}, data) {
// 触发a模块的mutation
commit('a/commitB', data, { root: true });
},
setApp({commit, dispatch, state, rootState, rootGetters}, data) {
// 触发a模块的action
dispatch('a/actionB', data, { root: true });
// 使用a模块的state数据
console.log(rootState.a.stateData);
// 使用a模块的getter数据
console.log(rootGetters['a/xxx'])
}
}
四、vuex的动态加载模块
可以通过store.registerModule来实现动态加载c模块:
import store from '@/store/index'
store.registerModule('c', {
state: {
count3: 3
},
mutation,
action,
getters
})
// 引入
computed: {
...mapState({
count3: state => state.c.count3
})
},
// 可以通过unregisterModule解绑
// store.unregisterModule('c');
五、持久化数据
vuex能很好的实现多个视图间数据共享,但是如果刷新页面,vuex中的state数据会重新初始化,有些场景我们希望刷新页面数据保持不变(比如面包屑导航),可以使用localStorage存储数据(但是localStorage存储需要写很多setItem、getItem),也可以使用vuex结合vuex-persistedstate插件实现数据持久化,下面来看下vuex-persistedstate是如何使用的:
首先安装插件:
npm install vuex-persistedstate --save
使用vuex-persistedstate默认会将数据存储到localStorage:
import Vue from 'vue'
import Vuex from 'vuex'
import persistedState from 'vuex-persistedstate'
Vue.use(Vuex)
const state = {
activeIndex: "0", // 当前选中tab
activeMenuIndex: 0, // 当前选菜单下标
activeMenuSubIndex: 0, // 当前选子菜单下标
}
...
export default new Vuex.Store({
modules: {
pageset,
},
state,
mutations,
actions,
getters,
plugins: [persistedState()]
})
当然可以修改storage配置将数据存储到sessionStorage:
import persistedState from "vuex-persistedstate"
export default newVuex.Store({
modules: {
pageset,
},
state,
mutations,
actions,
getters,
plugins: [
persistedState({
storage: window.sessionStorage
})
]
})
默认情况下所有的数据都会持久化保存,可以通过修改配置选择需要持久化的数据:
import persistedState from "vuex-persistedstate"
export default newVuex.Store({
modules: {
pageset,
},
state,
mutations,
actions,
getters,
plugins: [
persistedState({
storage: window.localStorage,
reducer(val) {
return {
// 只储存state中的token
assessmentData: val.token
}
}
})
]
})
// 也可以选择某个vuex模块进行持久化存储
const store = new Vuex.Store({
modules: {
pageset,
modules2,
},
state,
mutations,
actions,
getters,
plugins: [
persistedState({
key: "per-vuex", // 浏览器中的名字
paths: ["pageset", "modules2"] // 需要存储起来的参数模块
})
]
});
六、vuex 其他配置
1.当使用commit或者dispatch更新vuex中的数据时, 模板渲染到界面的数据也会发生变化, 并且会刷新整个界面, 这时可以使用vuex热更替功能:
// store/index.js
import { createStore } from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
import pageset from './pageset/index'
...
const store = createStore({
state,
mutations,
actions,
getters,
modules: {
pageset: pageset
}
})
if(module.hot) {
// accept传入的数组里面是上面引入文件的路径
module.hot.accept(
[
'./state',
'./mutations',
'./actions',
'./getters',
'./pageset/index'
], () => {
// 获取更新后的模块
// 因为 babel 6 的模块编译格式问题,这里需要加上 `.default`
const newState = require('./state').default;
const newMutations = require('./mutations').default;
const newActions = require('./actions').default;
const newGetters = require('./getters').default;
const newPageset = require('./pageset/index').default;
// 加载新模块
store.hotUpdate({
state: newState,
mutations: newMutations,
actions: newActions,
getters: newGetters,
modules: {
pageset: pageset
}
})
}
)
}
export default store;
2.store的watch监听: 不同于页面的watch监听, 这里的watch需要传入两个函数, 第一个函数相当于一个getter函数, 当第一个函数的返回值发生变化的时候会触发第二个函数调用。
import store from '@/store/index'
store.watch((state) => state.count+1, (newCount) => {
// 当 (state) => state.count+1的返回值有变化的时候, 会执行这里的逻辑
console.log('新的值:', newCount);
})
3.store的subscribe订阅: subscribe订阅会拿到所有mutation的变化,每次有一个mutation被调用,都会执行subscribe回调函数。
// store/index.js
export default newVuex.Store({
modules: {
pageset,
},
state,
mutations,
actions,
getters,
plugins: [
(store) =>{
// vuex初始化的时候会调用plugins
store.subscribe(() => {
// mutation.type是当前被调用的mutation名称,mutation.payload是当前被调用的mutation的参数。
store.subscribe((mutation, state) => {
console.log(mutation.type);
console.log(mutation.payload);
})
})
}
]
})
同理也可以通过subscribeAction订阅来监听到所有action的调用:
import store from '@/store/index'
// action.type是当前被调用的action名称,action.payload是当前被调用的mutation的参数。
store.subscribeAction((action, state) => {
console.log(action.type);
console.log(action.payload);
})
其他使用见官方文档: vuex.vuejs.org/zh/guide/ho…