Vuex3 / Vuex4
文章目录
安装
npm install vuex -S
Vuex 属性
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: 100
},
mutations: {
setUser (state) {
state.user++
},
setUser2 (state, payload) {
state.user = payload
}
},
actions: {
},
modules: {
}
})
state
容器中的 state ⽤于存储需要在组件间共享的数据,特点如下:
- 容器中的数据可以被任意组件访问。
- 容器中的数据为响应式数据。
在组件中通过vm.$store.状态名
访问。
Mutation
更改 Vuex 的 store 中的状态的唯⼀⽅法是提交 mutation。Vuex 中的 mutation ⾮常类似于事件:每个 mutation 都有⼀个字符串的 事件类型 (type) 和 ⼀个 回调函数 (handler)。
这个回调函数就是我们实际进⾏状态更改的地⽅,并且它会接受 state 作为第⼀个参数。Mutation 必须为同步函数。如果需要进⾏异步操作,则需要使⽤ Vuex 的 Action。
- 如果要修改 Vuex 中的 state,必须提前定义 Mutation 函数,需要时再进⾏提交(触发)。
Mutation 接收 state 对象为第⼀个参数,⽤于操作 state 内的数据。 - Mutation 还接收 提交载荷(payload)作为第⼆个参数,指的是 commit() 传⼊的额外数据,常在需要根据上下⽂数据修改 state 时使⽤。
在组件中通过 vm.$store.commit('Mutation名称')
提交 Mutation,执⾏操作。
Actions
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,⽽不是直接变更状态。
- Action 可以包含任意异步操作。
Action 函数接受⼀个与 store 实例具有相同⽅法和属性的 context 对象,因此你可以调⽤
context.commit 提交⼀个 mutation。
Action 通过 vm.$store.dispatch
⽅法触发,参数1为 action 名称,参数2为 payload
// store/index.js
actions: {
addAction (context) {
setTimeout(function () {
context.commit('setUser')
}, 1000)
}
}
组件访问存在 Vuex 中的公共状态
// login/index.vue
methods: {
async onSubmit () {
console.log(this.$store.state.user)
this.$store.commit('setUser')
this.$store.commit('setUser2', '示例内容')
this.$store.dispatch('addAction')
}
}
Getter 与 Module
Vuex 核⼼概念还有 Getter 与 Module 功能,可通过⽂档学习。
一个栗子 关于登录信息的存储和使用
组件保存登录信息到 Vuex 中 — login/index.vue
// view/login/index.vue
if (data.state===1){
// 当登录成功时,记录登录状态,存储到 Vuex 中
this.$store.commit('setUser', data.content)
this.$message.success('登录成功')
this.$router.push(this.$route.query.redirect || '/')
} else {
this.$message.error('登录失败 ' + data.message)
}
// main.js
import store from './store'
app.use(store)
// view/login/index.vue
import { useStore } from 'vuex'
const store = useStore()
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// 成功时,通过 mutation 提交新的 token 信息
store.commit('user/setUser', data.data.token)
// 跳转页面
router.push(route.query.redirect ?? '/user')
Vuex 设置 — store/index.js
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
// 将 user 的初始值更改为本地存储获取 user 的数据。
// 需要考虑不存在 user 数据的初始情况
user: JSON.parse(window.localStorage.getItem("user") || null)
},
mutations: {
setUser (state, payload) {
state.user = JSON.parse(payload)
// 将状态通过本地存储⽅式对 user 进⾏数据持久化,避免⻚⾯刷新后状态丢失。
// - 注意,本地存储只能保存字符串
window.localStorage.setItem("user", payload)
}
},
actions: {
},
modules: {
}
})
不封装
封装成 modules
// store/index.js
import { createStore } from 'vuex'
// 将封装的状态模块引入
import user from './modules/user'
import cart from './modules/cart'
export default createStore({
// 添加 modules 选项
modules: {
user,
cart
}
})
// store/modules/user.js
const state = {
// 用户 Token 信息
token: window.localStorage.getItem('USER_TOKEN'),
}
const getters = {}
const mutations = {
// 用户功能:设置用户 Token
setUser (state, payload) {
state.token = payload
window.localStorage.setItem('USER_TOKEN', payload)
}
}
const actions = {}
export default {
namespaced: true,
state,
getters,
mutations,
actions
}
路由检查登录 — Vue Router 的导航守卫 beforeEach
路由跳转时,需要校验登录状态,并根据结果进⾏后续处理。
这⾥使⽤ Vue Router 的导航守卫 beforeEach ,在任务导航被触发时进⾏登录状态检测。
当前后台页面均需要登录状态,但如果需求中只有部分页面需要登录状态的话,该如何判断处理呢? 我们可以通过 Vue Router 中的 路由元信息 功能来设置。
下面给需要登录状态的路由添加路由元信息
- meta ⽤于保存与路由相关的⾃定义数据
- requiresAuth 表示是否需要认证,true 为需要认证
// router/index.js
// ⽤户登录状态保存在 store (Vuex) 中,引⼊⽂件读取数据检测。
import store from '@/store'
const router = new VueRouter({
routes
})
// 全局前置守卫
router.beforeEach((to, from, next) => {
console.log('to:', to)
console.log('from:', from)
// 官⽅示例,检测路由是否需要登录
if (to.matched.some(record => record.meta.requiresAuth)) {
// 检测 store 中的 user是否存在
if (!store.state.user) {
// 未登录,导航跳转到登录⻚
next({
name: 'login',
// 通过 query 属性给 URL 设置查询字符串参数(键值均为⾃定义)
query: {
// path 仅包含路径,fullpath 为完整 url(包含查询字符串参数等信息)
// 存到 redirect中去。通过 this.$route.query.redirect 访问
redirect: to.fullPath
})
} else {
// 已经登录,允许通过
next()
}
} else {
// ⽆需登录,允许通过
next()
}
})
const routes = [
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: 'login' */'@/views/login/index')
},{
path: '/menu',
name: 'menu',
component: () => import(/* webpackChunkName: 'menu' */'@/views/menu/index'),
meta: { requiresAuth: true }
}]
// router/index.js
import store from '@/store'
// 导航守卫
router.beforeEach(to => {
// 对无需登录的页面进行放行
if (!to.meta.requireAuth) {
return true
}
// 校验登录状态
if (!store.state.user && !window.localStorage.getItem('USER_TOKEN')) {
// 跳转登录页,同时记录当前位置
return {
name: 'login',
query: {
redirect: to.fullPath
}
}
}
})
路由检查登录 — 登录成功后跳转到之前的页面
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
router.push(route.query.redirect ?? '/user')
请求检查登录 — 给 headers 设置 token
Token 是⼀种常⽤的接⼝鉴权⽅式。
Token 是在⽤户登录成功后,由服务端⽣成的⼀段保存了⽤户身份信息的、加密的字符串。
跟多请求都需要在 header 设置 Token 信息,可以通过 Axios 拦截器统⼀处理。
Axios 拦截器与导航守卫相似,可以在任意请求和响应前进⾏拦截处理,功能分为请求拦截器与响应拦截器。
通过请求拦截器参数 config.headers 可以访问请求头,将 store 中的 Token 统⼀设置即可。
(本来要在request中的各个api中设置headers)
/* headers: {
Authorization: store.state.user.access_token
} */
// utils/request.js
import store from '@/store'
// 设置请求拦截器
request.interceptors.request.use(config => {
// 读取 store 中存储的 user 数据
const { user } = store.state
// 检测 user 是否存在数据,如果有,则进⾏ token 设置
if (user && user.access_token) {
config.headers.Authorization = user.access_token
}
return config
})
// 引入 router
import router from '@/router'
// 在响应拦截器中进行失败处理
request.interceptors.response.use(config => {
// 根据我们的后端响应数据,发现响应的状态信息为 410000 时,说明用户未登录访问了相关接口
// 跳转登录页
if (config.data.status === 410000) {
router.push({
name: 'login',
query: {
redirect: router.currentRoute.fullPath
}
})
}
return config
})
请求检查登录 — Token 过期了怎么办
Token 具有过期时间,过期后 Token ⽆法继续使⽤。
将登录成功后响应的 refresh_token 发送给刷新 Token 接⼝获取新的 access_token,再利⽤新的 access_token 进⾏接⼝鉴权。
通过响应拦截器刷新 Token
Token 过期可能发⽣在任意接⼝操作时,可通过 Axios 响应拦截器统⼀处理。
步骤如下:
- 判断是否为 Token 过期导致状态码为 401
- 获取 refresh_token
- 请求刷新 Token 接⼝
- 记录刷新状态,避免多个接⼝重复刷新 Token
import axios from 'axios'
import store from '@/store'
import router from '@/router'
import qs from 'qs'
import { Message } from 'element-ui'
const request = axios.create({
// timeout: 5000
// baseURL:
})
let isRefreshing = false
let rqs = []
// 设置响应拦截器
request.interceptors.response.use(function (response) {
// 状态码为 2xx 都会进⼊这⾥
// console.log('请求响应成功了:', response)
return response
}, function (error) {
// 超出 2xx 都会进⼊这⾥
if (error.response) {
// 1. 保存状态码
const { status } = error.response
// 2. 判断
let errorMessage = ''
errorMessage = '请求参数错误'
if (status === 400) {
errorMessage = '请求参数错误'
} else if (status === 401) {
// token ⽆效
// 1. without token
if (!store.state.user) {
// redirect
redirectLogin()
return Promise.reject(error)
} else {
// 2. wrong token (exsiting refresh_token)
// 发送刷新请求前判断 isRefreshing 是否存在其他已发送的刷新请求
// 1 如果有,则将当前请求挂起,等到 Token 刷新完毕再重发,这⾥先设置为 return
if (isRefreshing) {
return rqs.push(() => {
request(error.config)
})
}
// 2 如果没有,则更新 isRefreshing 并发送请求,继续执⾏后续操作
isRefreshing = true
return request({
method: 'POST',
url: '/front/user/refresh_token',
data: qs.stringify({
refreshtoken: store.state.user.refresh_token
})
}).then(res => {
if (res.data.state !== 1) {
store.commit('setUser', null)
redirectLogin()
} else {
store.commit('setUser', res.data.content)
// Token 刷新成功后,将 requests 中的请求重新发送
rqs.forEach(callback => callback())
rqs = []
// 这时再对之前报 401 的接⼝重新请求,同时 return
// - error.config 是之前失败的请求的配置信息
// - request() 内部已经将原请求的所有功能包含了,⽆需接收结果返回。
return request(error.config)
}
}).catch(() => {
// 这⾥⽤于处理 HTTP 报错的情况(我们的服务器使⽤的为响应⾃定义状态 -1来标识失败)
// - 此处的操作与 then() 中失败的处理⽅式相同
// - 由于不需要使⽤ catch 参数中的错误信息 err,所以没有接收
store.commit('setUser', null)
redirectLogin()
return Promise.reject(error)
}).finally(() => {
// 3 请求完毕,⽆论成功失败,设置 isRefreshing 为 false
isRefreshing = false
})
}
} else if (status === 403) {
errorMessage = '没有权限,请联系管理员'
} else if (status === 404) {
errorMessage = '请求资源不存在'
} else if (status >= 500) {
errorMessage = '服务端错误,请联系管理员'
}
Message.error(errorMessage)
} else if (error.request) {
// 请求发送成功,但没有收到响应
Message.error('请求超时,请重试')
} else {
// 在设置请求时发⽣了⼀些失去,触发了错误(未知型错误)
Message.error(`请求失败${error.message}`)
}
// console.dir(error)
return Promise.reject(error)
})
第二个栗子 管理购物车
v-model 需要去修改 vuex 中的数据,通过计算属性中的 get 和 set 方法。get 去访问 store 的数据,set 去 commit 一个 mutation 或者dispute 一个 action。
利用 getters 对 store 里面的 state 状态进行计算并缓存。