Vuex3 / Vuex4 使用指南

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 核⼼概念还有 GetterModule 功能,可通过⽂档学习。


一个栗子 关于登录信息的存储和使用

组件保存登录信息到 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: {
  }
})

不封装

// store/index.js
封装成 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 状态进行计算并缓存。
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值