目录
一、Vuex
1.1、概述
1.2、Vuex 核心概念
1.3、安装与配置
1.4、在组件中获取 Vuex 中的数据
1.5、moutations 同步修改 state
1.6、actions 配置 mutations 异步修改 state 数据
1.7、getters
1.8、modules 模块化
1.9、辅助函数
图例 :
我是因为啥才去用的 Vuex 呢 ?
一开始搭建项目,初期建设嘛,老前端提议说这个项目没那么大,用不着再引进来个
Vuex 库了,意思好像是说这个 Vuex 库会很大,轻易的话就不要引入项目了,就使用
Event Bus 事件总线 来处理就够用了 , 但是吧 , 项目写着写着 ,项目目录越来越多 ,
甚至到了后期阶段性优化 , 以及细拆分组件时 , 导致数据传递起来越来越不方便了 ,
就感觉有必要引入 Vuex 来管理数据了。
引用 Redux 的作者 Dan Abramov 的话说就是 :
Flux 架构就像眼镜:您自会知道什么时候需要它。
Vuex 是什么? | Vuex
Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式 ,Vuex 是实现 组件 全局状态( 数据 ) 管理 的一种 机制 ,可以方便的 实现组件之间 的 数据共享 。如果您不打算 开发 大型 单页 应用,使用 Vuex 可能是繁琐冗余的 。如果您的应用 够简单,最好不要使用 Vuex 。
使用 Vuex 管理数据 优势 :
Vuex 对象中通过 state 来 存储 状态 ,除了 state 以外还有
用来 操作 ( mutations ) state 中数据的 方法集 ,以及当我们需要
对 state 中的 数据 需要 加工 ( getters ) 的 方法集 等等 成员 。
成员列表:
- state 存放 状态 ( 数据 ) 必须的
- mutations state 成员操作 -- 修改 state 中的 数据 同步 操作
- getters 加工 state 成员给外界 -- 获取 state 中的数据,类似于组件中的 计算属性
- actions 异步 操作 在 Vuex 中可以进行 异步操作(ajax)异步操作不能直接修改 state
- modules 模块化状态管理 多状态文件 管理时使用
Vuex 工作流程 :
首先 ,Vue Components( Vue 组件 )如果调用某个 VueX 的方法过程中需要向 后端 发送请求时 ( Backend API ) 或者 说出现 异步操作 时 ,则走上面的分支流程 :需要先 Dispatch ( 派发 ) VueX 中 Actions 内编写的方法 ,以保证数据的同步。可以说,Actions 的存在就是为了让 Mutations 中的方法能在 异步 操作 中起作用 。( 可理解 : 把异步 转成了 同步 )
如果没有 异步操作 ,那么我们就可以直接在 组件内 提交 状态中的 Mutations 中自己 编写 的 方法 来达成对 state 成员的 修改 操作 。
面试点 : 我可以直接修改 state 中的数据么 ?
因为 state 本身其实就是一个对象而已 , 所以按理是能够直接修改 state 中的数据 , 但是 ! 我们极其 不建议 在组件中直接对 state 中的成员进行操作 ,这是因为 直接修改 ( 例如:this.$store.state.name = 'hello' ) 的话不能被 Vue Devtools ( 开发者工具 ) 所监控到 。最后被修改后的 state 成员会被 渲染到组件的 原位置当中去。( 无法实时同步响应更新视图 )
通过 npm 安装
$ npm i -S vuex
手动创建仓库文件
一般是在项目的 src( 根目录 )/ store( 数据仓库 )/ index.js
index.js 就是 vuex 的 仓库 文件
( 当然 , 你要是勾选上了 Vuex 插件的话 , 这些操作都会自动帮你创建弄好了的 )
在一个模块化的打包系统中,必须显式地通过
Vue.use() 插件
来安装 Vuex:① 引入 Vue 和 Vuex , 将 Vuex 使用 use 插件的方式 挂载到 Vue 上
② 通过 new 关键字 创建一个 Store 的 实例 ,
做好全局配置 ( 定义了全局 统一的、唯一的 数据源 )
③ 导出这个 store 实例对象
④ 在 main.js ( 主入口文件 ) 内 , 在 Vue 的实例上 挂载 store
为了在 Vue 组件中访问
this.$store
property ,你需要为 Vue 实例提供创建好的 store 。
Vuex 提供了一个从 根组件 向 所有 子组件 ,
以
store
选项的方式 “ 注 入 ” 该 store 的机制 :
// Vuex 状态管理入口文件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 核心配置
export default new Vuex.Store({
// 定义全局统一数据源
state: {
// 存放状态 ( 数据 )
},
mutations: {
// state 成员操作 -- 修改 state 中的数据 同步操作
},
actions: {
// 异步操作 异步操作 在 Vuex 中可以进行异步操作(ajax),异步操作不能修改 state
},
getters: {
// 加工 state 成员给外界 -- 获取 state 中的数据,类似于组件中的 计算属性
},
modules: {
// 模块化状态管理 多状态文件管理时使用
}
})
1、在 组件中 可以通过 this.$store.state.xxx 得到 Vuex 中 state 内的 数据 ,
2、在 组件中 一般通过 计算属性 ( computed ) 来获取 。( 推荐 )
mutations 同步 修改 state 数据 ,不能进行 异步
巧记 :
右侧组件 commit 的 参数 1 都是对应 mutations 内的 方法名称 ( ' incr ' ) ,
右侧组件 commit 的 参数 2 都是对应 mutations 内的 参数2 数据 ( 10 ) ;
左侧 Vuex 内 mutations 内 方法名称的对应函数 ( incr ) ,
参数 1 都是 state ( Vuex 的 state数据源 ) ,
参数 2 都是 commit 传过来的 参数 2 对应的 数据
在组件生命周期中初始化时通过 dispatch 发送指令 给 actions 来完成 异步 请求 。
请求得到数据后,通过 commit 方法 通知 mutations 修改 state 数据 ,state 数据又是响应式 。而组件中通过 计算属性 获取数据,所以就 实时更新 数据状态
在 组件中 发送 dispatch 和 展示
巧记 :
右侧组件 dispatch 的 参数 1 都是对应 actions 内的 方法名称 ( ' fetchUser ' ) ,
右侧组件 dispatch 的 参数 2 都是对应 actions 内的 参数2 数据 ; ( 1 )
左侧 Vuex 内 actions 内 方法名称的对应函数 ( fetchUser ) ,
参数 1 是 store ( 可将我们需要用到的 { commit } 解构出来单独使用 ) ,
参数 2 是 dispatch 传递过来的 参数 2 的数据 (可选参数)
巧记 :
下面 异步操作的 ( actions 内的 ) commit 的
参数 1 都是 方法名称 ( ' setUsers ' ) ,
参数 2 都是 数据 ( 异步请求回来的数据 ret ) ;
上面 同步操作的 ( mutations内的 )方法名称 对应的函数 ( setUsers ) ,
参数 1 都是 state ( Vuex 的 state数据源 ) ,
参数 2 都是 commit 传过来的 参数 2 对应的 数据 ( ret => payload )
类似于 组件中 的 计算属性 computed
this.$store.getters.xxx
模块化 ,在 大型项目 中 会用到 ,把 state 数据 拆分到不同的 modules 模块中 ( 不同文件中 )
把原来也在 Vue.stroe 实例配置中的代码 ,拆分提取到不同的 业务文件 中
默认 模块化 没有开启强制 命名空间 : :
namespaced: true 开启强制命名空间 写法 : :
知识点 :
JavaScript _ Object.keys( )_雨季mo浅忆的博客-CSDN博客
reduce _ 高阶函数 之 聚合运算_雨季mo浅忆的博客-CSDN博客
ES5 和 ES6新增_雨季mo浅忆的博客-CSDN博客
正则表达式 _ 基础版_雨季mo浅忆的博客-CSDN博客
9. 字符串和正则合作的方法
// 自动导入 Vuex 中的模块
let moduleFn = require.context('./modules', false, /\.js$/i)
console.log(moduleFn) // 是一个函数 : f webpackContext(req) {...}
// webpackContext.keys = function webpackContextKeys() { return Object.keys(map) }
console.log(moduleFn.keys()) // 是一个数组 : [ "./count.js", "./films.js", "./xxx.js"]
let modules = moduleFn.keys().reduce((prev, curr) => {
console.log(prev, curr)
// {} , "./count.js"
// {count: {...}} , "./films.js"
// {count: {...}}, films: {...} , "./xxx.js"
let value = { ...moduleFn(curr).default, namespaced: true }
// 字符串方法和正则表达式中的分组
console.log(curr.match(/\.\/(\w+)\.js/i))
// (2) ["./count.js", "count", index: 0, input: "./count.js", group: undefined]
// (2) ["./films.js", "films", index: 0, input: "./films.js", group: undefined]
// (2) ["./xxx.js", "xxx", index: 0, input: "./xxx.js", group: undefined]
let key = curr.match(/\.\/(\w+)\.js/i)[1]
console.log(key)
// count
// films
// xxx
prev[key] = value
console.log(prev)
// {count: {...}}
// {count: {...}, films: {...}}
// {count: {...}, films: {...}, xxx: {...}}
return prev
}, {})
- mapState
- mapGetters
- mapActions
- mapMutations
- 注:模块化后,开启了 namespaced: true 配置后 ,注意方法的调用有 2 个参数
- 注意 : 后面 都有 s , 是复数
对象 ( 不常用 )
提升部分 ( 了 解 )
自定义变量操作方法名 :
多行暴露 :
这里注意一点 :
经测试好像是只能通过 多行暴露 或者 统一暴露 的形式将其导出
ECMAScript 6 _ 模块化_雨季mo浅忆的博客-CSDN博客_模块化开发理解
这里一定要注意看仔细了 :
经过本人后来测试 , 好像只有红线方案生效了, 黑色方案会产生报错
所以一定要这样使用 :
...mapMutations( ' 命名空间名称 ' , [ 变量名 ] ) 这里 变量名 不要 加引号 “”
this.[ 变量名 ]( 参数 ) 如此 调用 并 传参
否则 // 错误调用写法 " TypeError: this.xxx is not a function "
src / store / index.js
// Vuex 状态管理入口文件
import Vue from "vue";
import Vuex from "vuex";
import demo from './modules/demo'
Vue.use(Vuex);
// 核心配置, 实例化一个 Vuex 状态
// Vuex 实例配置选项中的配置, 可以都不写, 但是 state 还是规定必须要有的
export default new Vuex.store({
// 定义全局统一数据源
state: {
demo: "hello_Vuex",
},
// 加工 state 数据给外界 (Vuex 中的计算属性, 专门用于数据获取所用)
getters: {
getDemo(state) {
return state.demo
}
// getDemo: state => state.demo
},
// 专门用于同步修改 state 中的数据操作
mutations: {
// state 参数1, 就是当前状态对象
// 参数2, 组件内通过 commit 方法传过来的数据(可选参数)
setDemo(state, payload) {
state.demo = payload
},
},
// 异步操作
actions: {
// updateDemo({ commit }, payload) {
// commit("setDemo", payload);
// },
},
// 模块化状态管理
modules: {
// 模块化状态管理 多状态文件管理时使用
/* 模块化后, state 它就会有命名空间, 获取时一定要加命名空间, 否则读取不到,
但是默认 getters, mutations, actions, 它们没有命名空间, 只有你强制要求开启它们时, 它们才会有
=> namespaced: true 强制开启命名空间 */
// key: value
// key 就是命名空间名称, value 子模块对象
// 这里注意如果是 外部文件的话一定要先引入进来才行
demo
}
});
src / store / modules / demo.js
export default {
namespaced: true, // 开启命名空间
state: {
modulesDemo: '模块化里的_demo',
},
getters: {
// getDemo(state) {
// return state.demo
// },
getModulesDemo: state => state.modulesDemo
},
mutations: {
}
}
1、直接使用 this.$store.state.xxx 来获取
2、通过在 computed 计算属性中使用 this.$store.getters.xxx 去获取
3、使用 modules 模块化 后的获取方式
this.$store.state.命名空间名称.xxx
4、利用 辅助函数 来 获取
4-1、在 computed 计算属性 中使用 ...mapState 获取 ( 数组 | 对象 )
4-2、在 computed 计算属性 中使用 ...mapGetters 获取( 数组 | 对象 { 可自定义名称 } )
☆ 5、辅助函数 ...mapXxx + 命名空间 namespaced 结合使用 来获取
☆ 5-1、...mapState ( 命名空间名称 , [ " state 内变量名 " ]
☆ 5-2、...mapGetters ( 命名空间名称 , [ " getters 内方法名 " ]
异步 ( Vuex _ todolist )
Vuex 中可能会需要的 utils :
( 1 ) : sessionStorage ( 会话存储 ) 封装方案 :
class SessionStorage {
// 获取
get(key) {
let value = sessionStorage.getItem(key) || ''
if (/^[\[\{]/.test(value)) {
value = JSON.parse(value)
}
return value
}
// 设置
set(key, value) {
if (typeof value == 'object') {
value = JSON.stringify(value)
}
sessionStorage.setItem(key, value)
}
// 删除
del(key) {
sessionStorage.removeItem(key)
}
}
// 默认导出一个 类 的 实例
export default new SessionStorage;
通过 storeage 来封装一些 操作 token 的 方法 :
import storage from './sessionStore'
const key = 'token'
// 设置 token 值
export const setToken = token => storage.set(key, token)
// 获取 token 值
export const getToken = () => storage.get(key)
// 判断是否含有 token 值
export const hasToken = () => storage.get(key) === '' ? false : true
// 删除 token 值
export const delToken = () => storage.del(key)
必须模块化 :辅助函数 + 命名空间 结合 使用
( 加点自动化语法 , 自动化导入 modules 中的文件模块 )
( 引入统一配置变量名文件 )
src / store / index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
// 自动导入 Vuex 中的模块
let moduleFn = require.context("./modules", false, /\.js$/i);
let modules = moduleFn.keys().reduce((prev, curr) => {
let value = { ...moduleFn(curr).default, namespaced: true };
// 字符串方法和正则表达式中的分组
let key = curr.match(/\.\/(\w+)\.js/i)[1];
prev[key] = value;
return prev;
}, {});
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
// 模块化配置
modules,
});
src / store / typings.js
// 可自定义 N 多个操作变量
// ① 多行暴露
// export const SET_MAPNAME = "set_mapName";
// ② 统一暴露
const SET_MAPNAME = "set_mapName";
export { SET_MAPNAME };
src / store / modules / mapNameSpaced.js
// 引入变量名文件
import { SET_MAPNAME } from "../typings";
export default {
// 强制开启命名空间
// namespaced: true,
state: {
modulesMapName: "state--modules命名空间里的内容",
},
getters: {
getModulesMapName: (state) => state.modulesMapName + "+++getModulesMapName",
},
mutations: {
setModulesMapName(state, payload) {
state.modulesMapName = payload;
},
// 变量名充当函数名
[SET_MAPNAME](state, payload) {
state.modulesMapName = payload;
},
},
actions: {},
};
demo 任意子组件内
获取数据的操作方法 :
{{ mapName }}
{{ modulesMapName }}
{{ getModulesMapName }}
修改数据的操作方法 :
完整代码 :
{{ mapName }}
{{ modulesMapName }}
{{ getModulesMapName }}
// 引入变量名文件
import { SET_MAPNAME } from "../typings";
export default {
// 强制开启命名空间
// namespaced: true,
state: {
modulesMapName: "state--modules命名空间里的内容",
},
getters: {
getModulesMapName: (state) => state.modulesMapName + "+++getModulesMapName",
},
mutations: {
setModulesMapName(state, payload) {
state.modulesMapName = payload;
},
// 变量名充当函数名
[SET_MAPNAME](state, payload) {
state.modulesMapName = payload;
},
},
actions: {},
};
// 可自定义 N 多个操作变量
// ① 多行暴露
// export const SET_MAPNAME = "set_mapName";
// ② 统一暴露
const SET_MAPNAME = "set_mapName";
export { SET_MAPNAME };
1、问题描述:
一般在登录成功的时候需要把 用户信息 ,菜单信息 放置 Vuex 中 ,作为全局的共享数据。
但是在页面刷新的时候 Vuex 里的数据会重新初始化 ,导致数据丢失 。
因为 Vuex 里的数据是保存在运行内存中的 ,当页面刷新时 ,页面会重新加载 Vue 实例 ,
Vuex 里面的数据就会被重新赋值 。
2、解决思路:
办法一:将 Vuex 中的数据直接保存到浏览器缓存中(sessionStorage、localStorage、cookie)
办法二:在页面刷新的时候再次请求远程数据 ,使之动态更新 Vuex 数据
办法三:在父页面向后台请求远程数据 ,并且在页面刷新前将 Vuex 的数据
先保存至 sessionStorage(以防请求数据量过大页面加载时拿不到返回的数据)
分析:
办法一的缺点是不安全 ,不适用大数据量的存储;
办法二适用于少量的数据 ,并且不会出现网络延迟;
办法三是要讲的重点 ,办法二和办法一 一起使用。
3、使用持久化插件
@2 vuex-persistedstate
原理:
将 Vuex 的 state 存在 localStorage 或 sessionStorage 或 cookie 中一份
刷新页面的一瞬间,Vuex 数据消失 ,Vuex 会去 sessionStorage 中拿回数据 ,
变相的实现了数据刷新不丢失 ~
使用方法:
安装:$
npm install vuex-persistedstate --save
在 store 下的 index.js 中 ,引入并配置
import createPersistedState from "vuex-persistedstate" const store = new Vuex.Store({ // ... plugins: [createPersistedState()] })
此时可以选择数据存储的位置 ,可以是 localStorage / sessionStorage / cookie ,
此处以存储到 sessionStorage 为例 ,配置如下:
import createPersistedState from "vuex-persistedstate" const store = new Vuex.Store({ // ... plugins: [createPersistedState({ storage: window.sessionStorage })] })
存储指定 state:
vuex-persistedstate 默认持久化所有 state ,指定需要持久化的 state , 配置如下:
import createPersistedState from "vuex-persistedstate" const store = new Vuex.Store({ // ... plugins: [createPersistedState({ storage: window.sessionStorage, reducer(val) { return { // 只储存state中的user user: val.user } } })]
此刻的 val 对应 store / modules 文件夹下几个 js 文件存储的内容 ,
也就是 store / index 中 import 的几个模块 ,可以自己打印看看。
希望哪一部分的数据持久存储 ,将数据的名字在此配置就可以,
目前我只想持久化存储 user 模块的数据 。
注意:如果此刻想配置多个选项,将 plugins 写成一个一维数组,不然会报错。
import createPersistedState from "vuex-persistedstate" import createLogger from 'vuex/dist/logger' // 判断环境 vuex 提示生产环境中不使用 const debug = process.env.NODE_ENV !== 'production' const createPersisted = createPersistedState({ storage: window.sessionStorage }) export default new Vuex.Store({ // ... plugins: debug ? [createLogger(), createPersisted] : [createPersisted] })
@3 vuex-along
将 state 里的数据保存一份到本地存储
( localStorage、sessionStorage、cookie )
// App.vue
2、使用第三方插件如 vuex-along
( 本质上还是利用 session 等缓存到本地 )
import Vue from 'vue' import Vuex from 'vuex' import VueXAlong from 'vuex-along' Vue.use(Vuex) let productData = require('@/assets/api/list.json') const moduleA = { state: { a1: 'a1', a2: 'a2', } } export default new Vuex.Store({ state: { productList: [],//列表数据 cartList: [],//购物车数据 }, mutations: { //产品列表 setProductList(state, data){ state.productList = data }, //加入购物车 setCartList(state, id){ let cartIndex = state.cartList.findIndex(item => item.id === id) if(cartIndex < 0){ state.cartList.push({id, count: 1}) }else{ state.cartList[cartIndex].count++ } }, //删除购物车商品 deleteCartList(state, id){ let cartIndex = state.cartList.findIndex(item => item.id === id) state.cartList.splice(cartIndex, 1) }, //编辑购物车商品数量 editCartList(state, data){ let cartIndex = state.cartList.findIndex(item => item.id === data.id) state.cartList[cartIndex].count = data.count }, clearCart(state){ state.cartList = [] }, }, actions: { getProductList(context){ //模拟ajax请求,将返回的信息直接存储在store setTimeout(()=>{ context.commit('setProductList', productData) }, 2000) }, buy(context){ //模拟ajax请求通过返回promise对象将结果返回给操作提交的地方 return new Promise((resolve, reject) => { setTimeout(()=>{ context.commit('clearCart') resolve({msg:'下单成功', code: 200}) //reject({message: '提交失败', code: 300}) }, 1000) }) } }, modules: { ma: moduleA }, //缓存state的数据到storage plugins: [VueXAlong()], //全量的参数配置(sessionStorage 数据恢复优先级高于 localStorage) /* plugins: [VueXAlong({ // 设置保存的集合名字,避免同站点下的多项目数据冲突 name: 'my-app-VueXAlong', //过滤ma的数据将其他的数据存入localStorage local: { list: ['ma'],//需要监听的属性名或者模块名 isFilter: true,//是否过滤而非保存 }, //保存ma的a1数据到sessionStorage session: { list: ['ma.a1'], isFilter: false, }, //仅使用sessionStorage保存 //justSession: true, })] */ })