Vuex是一个专为Vue.js应用程序开发的状态管理模式.它采用集中式储存管理应用的所有组件的转台并以相应的规则保证装填以一中可预测的方式发生变化.
Vuex可以将组件中的某些属性、值或者方法拿出来统一去声明、统一去定义,在Vuex中去声明,设置一些方法以及一些逻辑运算,来修改这些组件用到的相同的值.
一、Vuex入门
1.1 初始化 Vuex 工程
1.1.1 新建项目命名 vuex-demo
vue create vuex-demo //vue创建项目
1.1.2 安装依赖和运行
进入工程目录
cd vuex-demo
安装依赖
npm i vuex -s
运行项目
npm run serve
1.2 读取状态值 state
每一个 Vuex 项目的核心就是 store(仓库, store 就是一个对象,它包含着你的项目中大部分的状态(state)
state 是 store 对象中的一个选项,是 Vuex 管理的状态对象(共享的数据属性)
1.2.1 在 src 目录下创建 store 目录,store 下创建 index.js 文件
编码如下:
import Vue from "vue";
import Vuex from "vuex"// 引入 Vuex 插件
Vue.use(Vuex);
const store = new Vuex.Store({// 注意V 和 S都是大写字母
// 存放状态(共享属性)
state: {
count: 1
}
})
export default store
1.2.2 修改 main.js,导入和注册 store
编码如下:
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store"
Vue.config.productionTip = false;
new Vue({
router,
store,//注册
render: (h) => h(App),
}).$mount("#app");
1.2.3 组件中读取 state 状态数据,修改 src\views\Home.vue
编码如下:
<template>
<div class="home">
<h1>count:{{$store.state.count}}</h1>
</div>
</template>
1.2.4 测试效果
关于Object(...) is not a function的报错 :
第一次重构项目的时候,莫名其妙的报了这个错,上网看别的文章说是因为vue,vuex版本的问题,之后就按他们说的降低版本,但是结果无济于事,依然报这个错.之后索性又重构了一次项目,安装依赖的时候,发现npm i vuex -s的方式安装不上,安装界面一跳就过去了,package.json中没有反应,就强行把"vuex": "^3.4.0"写进去,用npm i的方式安装的,这次运行项目就没有报错,上网找也没搞清楚我的这个情况,有大佬望告知.
1.3 改变状态值 mutation
在 store 的 mutations 选项中定义方法,才可以改变状态值
通过 $store.commit('mutationName') 触发状态值的改变.
1.3.1 修改 store/index.js , 在 store 中添加 mutations 选项
代码如下:
mutations: {
add(state) {
state.count++
},
subtraction(state) {
state.count--
}
}
1.3.2 修改 src\views\Home.vue ,调用 mutations 中add、subtraction方法
<template>
<div class="home">
<h1>count:{{$store.state.count}}</h1>
<button @click="add">加1</button>
<button @click="subtraction">减1</button>
</div>
</template>
<script>
// @ is an alias to /src
export default {
name: "Home",
components: {
},
methods: {
add() {
this.$store.commit('add')
},
subtraction() {
this.$store.commit('subtraction')
}
}
};
</script>
点击 加/减法 按钮,控制台和页面显示数字变化.
修改state,其他组件拿到的也是修改过的,比组件间通信方便很多.
但是,mutations只能添加同步的方法.
1.4 提交载荷 payload
可以向 $store.commit 传入额外的参数,即 mutation 的 载荷(**payload**)
1.4.1 修改 src\store 下的 index.js
mutations: {
add(state, n) {//n 为荷载
state.count += n
},
subtraction(state, n) {
state.count -= n
}
}
1.4.2 修改 views\Home.vue 组件
methods: {
add() {
this.$store.commit('add', 10)//提交荷载
},
subtraction() {
this.$store.commit('subtraction', 10)
}
}
1.5 分发Action
Action 类似于 mutation,不同在于:
Action 提交的是 mutation,而不是在组件中直接变更状态, 通过它间接更新 state
在组件中通过 this.$store.dispatch('actionName') 触发状态值间接改变
Action 也支持载荷
Action 可以包含任意异步操作
1.5.1 修改 store/index.js ,增加 actions 选项
actions: {
add1(context, n) {
// 触发 mutations 中的 increment 改变 state
context.commit('add', n)
},
subtraction1(context, n) {
//按需传值
context.commit('subtraction', n)
}
}
1.5.2 修改 views/Home.vue, 触发 action 改变值
methods: {
add() {
// this.$store.commit('add', 10)//提交荷载
// 触发 actions 中的 add 改变状态值
this.$store.dispatch('add1', 10)
},
subtraction() {
// this.$store.commit('subtraction', 10)
this.$store.dispatch('subtraction1',10)
}
}
1.6 派生属性 getter
有时候需要从 store 中的 state 中派生出一些状态。
例如:基于上面代码,增加一个 desc 属性,当 count 值小于 50,则 desc 值为 吃饭 ; 大于等于 50 小于100,则 desc 值为 睡觉 ; 大于 100 , 则 desc 值为 打豆豆 。这时就需要用到 getter 解决。
getter 其实就类似于计算属性(get)的对象.组件中读取 $store.getters.xxx
1.6.1 修改 store/index.js ,增加 getters 选项
注意:getters 中接受 state 作为其第一个参数,也可以接受其他 getter 作为第二个参数
getters: {
desc(state) {
if (state.count < 50) {
return '吃饭'
} else if (state.count < 100) {
return '睡觉'
} else {
return '打豆豆'
}
}
}
1.6.2 修改 views/Home.vue, 显示派生属性的值
<div class="home">
<h1>count:{{$store.state.count}}</h1>
<button @click="add">加1</button>
<button @click="subtraction">减1</button>
派生属性:{{$store.getters.desc}}
</div>
1.6.3 测试效果
点击 Home 页面的 触发改变 按钮,当 count 新增到 60 , desc 会显示为 睡觉
1.7 Module 模块化项目结构
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter 等.
1.7.1 工程模块化进行改造,在 store 下创建 modules 目录,该目录下创建 home.js
代码如下:
const state = {
count: 0
}
const mutations = {
add(state, n) {//n 为荷载
state.count += n
},
subtraction(state, n) {
state.count -= n
}
}
const actions = {
add1(context, n) {
// 触发 mutations 中的 increment 改变 state
context.commit('add', n)
},
subtraction1(context, n) {
//按需传值
context.commit('subtraction', n)
}
}
const getters = {
desc(state) {
if (state.count < 50) {
return '吃饭'
} else if (state.count < 100) {
return '睡觉'
} else {
return '打豆豆'
}
}
}
export default {//将整体变为一个对象作为默认成员导出
// 存放状态(共享属性)
state,
//派生属性
getters,
// 改变 state 状态
mutations,
actions,
}
1.7.2 修改 store\index.js, 导入 ./modules/home.js
const store = new Vuex.Store({
modules: {
home
}
})
1.7.3 修改 Home.vue,About.vue
<template>
<div class="home">
<!-- 模块化之后需要.home.count获取数据 -->
<h1>count:{{$store.state.home.count}}</h1>
<button @click="add">加10</button>
<button @click="subtraction">减10</button>
派生属性:{{$store.getters.desc}}
</div>
</template>
<template>
<div class="about">
<h1>This is an about page</h1>
<h1>count:{{$store.state.home.count}}</h1>
</div>
</template>
正常访问, 与重构前一样
二、会员管理系统-Vuex 版
只对登录,获取用户信息,退出等功能采用Vuex状态管理的方式
2.1 初始化项目
2.1.1 复制 mms 工程为 mms-vuex
2.1.2 安装 Vuex
npm i vuex -s
2.2 登录
使用Vuex的时候,需要配合localStorage使用,防止刷新页面的时候,数据丢失.
2.2.1 登录与 token 状态管理
在 src\utils\ 目录下创建 auth.js, 封装 token 和 用户信息工具模块
const TOKEN_KEY = 'stu-token'
const USER_KEY = 'stu-user'
//?获取token
export function getToken() {
return localStorage.getItem(TOKEN_KEY)
}
//?保存token
export function setToken(token) {
return localStorage.setItem(TOKEN_KEY, token)
}
//?获取用户信息
export function getUser() {//getItem 拿到的是字符串,而用户信息需要一个对象
return JSON.parse(localStorage.getItem(USER_KEY))
}
//?保存用户信息
export function setUser(user) {//user 是一个对象
return localStorage.setItem(USER_KEY, JSON.stringify(user))
}
//?移除用户信息
export function removeToken() {
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(USER_KEY);
}
在 src 目录下新建 store\modules 目录,modules 下创建 user.js
//* 引入封装的方法
import { getToken } from "../../utils/auth";
import { setToken } from "../../utils/auth";
import { getUser } from "../../utils/auth";
import { setUser } from "../../utils/auth";
import { removeToken } from "../../utils/auth";
//* 引入 API login 中的方法
import { login, getUserInfo, logout } from "@/api/login.js"
const user = {
state: {
token: null,
user: null
},
mutations: {
SET_TOKEN(state, token) {
state.token = token
setToken(token)//配合localStorage使用
},
SET_USER(state, user) {
state.user = user;
//setUser(user);
},
},
actions: {
//登录获取token
Login({ commit }, form) {//替换登录的login方法 form是登录页面的表单
return new Promise((resolve, reject) => {
// 提交表单给后台进行验证是否正确
// resolve 触发成功处理,reject 触发异常处理
login(form.username, form.password).then(response => {
const resp = response.data
if (resp.flag) {//通过调用mutations中的方法修改state
commit('SET_TOKEN', resp.data.token)// 方法名称,载荷
resolve(resp)//成功返回 resp
}
}).catch(err => {
reject(err)
})
})
}
}
}
export default user
在 src\store 目录下创建 index.js
import Vue from 'vue';
import Vuex from 'vuex';
import User from './modules/user.js'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
User
}
})
修改 src 下的 main.js,导入和注册 store
//! 生成vue实例 相当于项目整个的入口
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import ElementUi from 'element-ui';//引入element-ui
import 'element-ui/lib/theme-chalk/index.css';//引入element-ui必需的css文件
import './premission.js';
import store from './store'
Vue.config.productionTip = false;
Vue.use(ElementUi);//使用element-ui
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");
重构登录组件 views\login\index.vue
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$store.dispatch('Login', this.form).then(response => {
if (response.flag) {
//response已经是response.data了 所以直接可以拿到flag
this.$router.push('/')
//信息存储在promise对象中做了,这里只需要做路由跳转就可以了
//并且之前的message提示,也做了统一异常处理.
}
})
} else {
console.log('error submit');
return false;
}
})
},
测试登录是否正常进入,并观察浏览器中 localStorage 是否有值
报错原因:
这个部分Vuex还没有做权限管理,浏览器没有token值的时候,我访问了首页,显示获取不到token,不加载首页,在token获取方法的位置找了半天,没找到错误= =,后来想起来浏览器如果本身就没有token值,不会跳转登录界面,重新登录解决.
2.2.2 解决刷新页面回到登录页面
当前在 permission.js 路由拦截中是通过浏览器 localStorage 获取 token 值,但现在使用的 Vuex 状态管理,需要通过 store 来获取 token 状态值.
在 src\permission.js 中将获取 token 方式替换为从 store 状态中获取,
如下:注意要 import 导入 store
...
import store from './store'//导入store
router.beforeEach((to, from, next) => {
// const token = localStorage.getItem('stu-token');
const token = store.state.user.token//权限管理的token不在本地获取,需要在Vuex中获取
...
重新访问登录 http://localhost:8888/loginhttp://localhost:8888/loginhttp://localhost:8888/login ,登录后一样可以进入首页,刷新首页,发现会回到登录页面。因为刷新 后 token 状态值还原初始值 null, 所以又会要求 重新登录.
解决刷新页面重新登录的问题,在 src\store\modules\user.js 中 token 初始值为 getToken()
state: {
token: getToken(),// getToken() 作为token初始值,解决刷新页面之后token为null
user: null
},
2.3 获取用户信息
之前获取用户信息是在登录中一起获取的,其实可以在权限拦截器( permission.js )中进行获取
2.3.1 修改 src\store\modules\user.js, 在 actions 中添加 GetUserInfo 方法
//通过token获取用户信息
GetUserInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getUserInfo(state.token).then(response => {
const respUser = response.data
commit('SET_USER', respUser.data)
resolve(respUser)
}).catch(err => {
reject(err)
})
})
}
2.3.2 修改 src\permission.js , 从 store 中获取用户数据
router.beforeEach((to, from, next) => {
// const token = localStorage.getItem('stu-token');
const token = store.state.user.token//权限管理的token不在本地获取,需要在Vuex中获取
if (!token) {//没拿到token,证明没有登录
if (to.path === '/login' || to.path === '/register') {
//如果当前路径在登录或者注册界面,只需要调用next方法来结束这个钩子函数
next();
} else {
next({ path: '/login' })//其它路径就跳转到 /login
}
} else {
if (to.path === '/login') {
next();
} else if (to.path === '/register') {
next();
} else {
store.dispatch('GetUserInfo').then(response => {
if (response.flag) {
next()
} else {//获取用户信息失败,跳转登录界面
next({ path: '/login' })
}
})
}
}
})
2.3.3 修改 src\components\AppHeader\index.vue 中data 选项中的 user 属性值
// user: JSON.parse(localStorage.getItem('stu-user')),
//获取本身为字符串,通过.parse()将字符串转换为对象.这样上面才能获取到nickname
user: this.$store.state.user.user,//前一个user代表组件化名称
重新登录后,本地存储中只有token,没有user.
token需要进行本地化 ,防止刷新页面的时候,路由变化的时候,token被删除,权限管理中,每次都通过token去获取user信息,即使Vuex被刷新,每次都会重新获取user信息,不会影响user信息的显示.
本地化之后,还需要用Vuex,user信息存在Vuex里进行状态管理,token也存在Vuex里,但token决定了权限管理,所以token要进行本地化,
2.4 退出
2.4.1 修改 src\store\modules\user.js, 在 actions 中添加 Logout 方法
//?退出登录
Logout({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(response => {
//将state置空,state中数据删掉,本地存储中的数据删掉
const resp = response.data
commit('SET_TOKEN', '')
commit('SER_USER', {})
removeToken();//将localStorage的内容删掉
resolve(resp)
}).catch(err => {
reject(err)
})
})
}
2.4.2 修改 methods 选项中的 handleCommand 方法
handleCommand(command) {
// this.$message('click on item ' + command);
switch (command) {
case 'a'://修改密码
this.handlepwd();
break;
case 'b'://退出登录
this.$store.dispatch('Logout').then(response => {
this.$router.push('login')
})
break;
}
}
退出系统之后,token信息删除.