Vue项目 Vuex、localStorage、VueRouter、前置守卫

573 阅读4分钟

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战

身份认证简介

  • 🤩避免"有门无墙",为了让登录功能变得有意义
    • 应当在用户登录成功后给用户生成一个标记(令牌),并将这个令牌保存起来
    • 在用户访问任意需要登陆的页面(组件)时都去验证令牌
    • 从而识别用户是否登录或是否有权访问对应功能
      • 成功,访问组件
      • 失败,进行提示

Vuex

  • 使用vue官方的状态管理工具vuex,实现让login组件中的数据被任意其他组件访问
  • 🤩vuex用来统一存储需要在多个组件间共享的状态(数据),状态可以被任意组件操作,是组件通信变得易如反掌

项目中使用vuex

  • 安装npm i vuex -S
  • 使用:创建vuex实例store(容器)
// store/index.js
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)
// 创建vuex容器实例,用来存储需要在组件中共享的状态
const store = new Vuex.store({
  state: {
    count: 0
  }
})

export default store
  • 在根vue实例中引入vuex作为插件
new Vue ({
  render: h => h(App),
  store
}).$mount("#app")
  • 通过vue.use()引入Vuex后,Vuex功能被注入到根实例下的所有子组件中,可通过$store访问内部功能

state

  • 容器中state用于存储需要在组件间共享的数据
  • 特点
    • 容器中的数据可以被任意组件访问
    • 容器中的数据为响应式数据
    export default new Vuex.store({
      state: {
        user: 'zs'
      },
      // 唯一提交state的方法
      mutations: {
      },
      // 异步操作
      actions: {
      },
      // 模块
      modules: {
      }
    })
    
  • 在组件中通过this.$store.state.状态名访问
async onSubmit () {
  console.log(this.$store.state.user)
}

Mutation

  • 要修改Vuex中的state,必须提前定义Mutation函数,需要提交时再进行提交
  • 🤩Mutation接收state对象为第一个参数,用于操作state内的数据
  • 代码演示
export default new Vuex.store({
  state: {
    age: 18
  },
  mutations: {
    // 第一步:定义mutation函数
    setAge (state) {
      state.age++
    }
  }
})
  • 需要提交(修改state的时候执行)
async onSubmit () {
  // 打印输出age
  console.log(this.$store.state.age)
  // 第二步:提交mutation,执行操作
  this.$store.commit('setAge')
  console.log(this.$store.state.age)
}
  • Mutation还接收提交载荷payload作为第二个参数,常在需要根据上下文数据修改state时使用
// store/index.js
mutations: {
  setAge (state,payload) {
    state.age = payload
  }
}

// 组件中
async onSubmit() {
  this.$store.commit('setAge',23)  
}

注意点

  • Mutation的设置方式使Vuex的状态修改有迹可循,易于维护。Vue DevTools提供了用于Vuex更高级的调试方式Time Travel

vue项目-vuex的mutation.png

  • 🤩Mutation必须是同步函数
    • 因为DevTools提供了Mutation日志功能,为了确保功能正常,内部不能存在异步任务,否则DevTools将无法得知Mutation的准确调用顺序。

Action

  • Action类似于mutation不同在于
    • Action提交的是Mutation,而不是直接变更状态
    • Action可以包含任意异步操作
  • 🤩Action函数接受一个与store实例具有相同方法和属性的context对象,因此你可以调用context.commit提交一个Mutation
// store/index.js
mutations: {
  setAge (state,payload) {
    // 修改状态
    state.age = payload
  }
},
actions: {
  // context与store实力具有相同的方法与属性
  addAction (context,payload) {
    setTimeout (function () {
      // 提交mutation
      context.commit('setAge',payload.age)
    }, payload.time)
  }
}
  • 🤩Action通过this.$store.dispatch方法触发
// login/index.vue
async onSubmit() {
  this.$store.dispatch('addAction',{ age: 10, time: 1000})
}

数据持久化

  • 🤩将状态通过本地存储方式localStorageuser进行数据持久化,避免页面刷新后状态丢失
state: {
  age: JSON.parse(window.localStorage.getItem('user') || null)
},
mutations: {
  setAge (state,payload) {
    // 转换为 对象 进行保存
    state.age = JSON.parse(payload)
    // 通过本地存储实现数据持久化
    window.localStorage.setItem('user',payload)
  }
}

校验页面访问权限

  • 🤩路由跳转时,需要验证登陆状态,使用Vue Router导航守卫beforeEach,在任务被触发时进行登录状态检测

  • 🤩如果需求中只有部分页面需要登陆状态的话,可以通过Vue Router中的路由元信息功能来设置

    • meta:保存与路由相关的自定义数据
    • requiresAuth: 是否需要认证,true需要认证
    // router/index.js 
    // 路由规则
     const routes = [
        {
          path: '/course',
          name: 'course',
          component: () => import(/* webpackChunkName: 'course' */'@/views/course/index.vue'),
          // 对需要身份验证的路由进行身份验证
          meta: { requiresAuth: true }
        }
      ]
    
  • 在导航守卫中检测to的路由是否需要登录

// 设置VueRouter的前置守卫,进行路由跳转权限验证
router.beforeEach((to, from, next) => {
  // 官方示例
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // 如果能从vuex中的store容器中取到身份信息,那么就身份认证成功
    if (!store.state.user) {
      console.log('未登录')
      next({
        // 需要跳转的路由名称
        name: 'login'
      })
    } else {
      // 已经登陆
      next()
    }
  } else {
    // 不要身份验证
    next() // 确保一定要调用 next()
  }
})

路由元信息优化操作

  • 如果某个父路由下的所有子路由均需要登录,可以直接给父路由设置meta处理(统一处理)
  • 因为子路由请求会经过父路由,直接给父路由设置登陆检测更加简单,适合所有子路由均需要登录的情况

登陆后跳转到上次访问页面

  • 应当在每次跳转到/login时记录当前to目标路由信息,通过跳转路由query属性进行设置
if (!store.state.user) {
  next({
    // 需要跳转的路由名称
    name: 'login',
    // 跳转路由需要携带的参数信息 redirect为自定义名称
    query: { redirect: to.fullPath }
  })
}