Vue预习课:统一状态管理 - Vuex

vuex的数据只是在内存(页面)中临时保存,一旦页面刷新则数据丢失

Vuex

Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式 。它采用集中式存储管理应用的所有组件的状
态,并以相应的规则保证状态以 可预测的方式发生变化

集中式存储为了保证数据的同步,因为多个组件都会使用到这个数据。方便组件使用同一个数据

如果使用其他的方式进行数据一层层传递,如果数据出现不同步问题,则不方便排查是那个组件出现问题

下面就是标准的数据流(数据的流向可以预测)
image.png

安装

在vue-cli的环境下安装vuex

  1. vue add vuex

起始

State —- 存储状态

将应用全局状态定义在state中

  1. state: {
  2. isLogin: false
  3. }

Mutation —- 变更状态

修改state只能通过mutation

  1. mutations: {
  2. login (state) {
  3. state.isLogin = true
  4. },
  5. logout (state) {
  6. state.isLogin = false
  7. }
  8. },

获取和修改状态

使用store.state获取状态

  1. <button @click="login" v-if="!$store.state.isLogin">登录</button>
  2. <button @click="logout" v-else>登出</button>

修改状态只能通过store.commit(mutation)

  1. this.$store.commit('login')
  2. this.$store.commit('logout')

vuex只是在当前内存中临时缓存,刷新页面,则vuex的状态丢失

Action —- 异步

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
    1. // 参数1是vuex传递的上下文context:{commit, dispatch, state}
    2. login ({ commit }, username) {
    3. // 模拟请求
    4. return new Promise((resolve, reject) => {
    5. setTimeout(() => {
    6. if (username === 'admin') {
    7. commit('login')
    8. resolve()
    9. } else {
    10. reject()
    11. }
    12. }, 1000);
    13. })
    14. }

派发动作触发actions

  1. // 派发动作触发actions admin是传递的参数
  2. this.$store.dispatch('login', 'admin').then(() => {
  3. this.$router.push(this.$route.query.redirect)
  4. }).catch(() => { // 登录失败
  5. alert('用户名或密码错误')
  6. })

最佳实践

模块化

程序很大得情况下:

使用modules定义多个子模块利于组件复杂状态

  1. import user from './user'
  2. export default new Vuex.Store({
  3. modules: {
  4. user,
  5. }
  6. })

移动先前登录状态相关代码到user.js

  1. export default {
  2. namespaced: true, // 设置独立的命名空间,避免命名冲突
  3. // ...
  4. }

访问方式响应变化

  1. // 访问数据的时候需要添加命名空间
  2. // Login.vue
  3. <button @click="login" v-if="!$store.state.user.isLogin">登录</button>
  4. this.$store.dispatch('user/login', 'admin').then(() => {
  5. const redirect = this.$route.query.redirect || '/'
  6. this.$router.push(redirect)
  7. }).catch(() => {
  8. alert('用户名或密码错误')
  9. })
  10. this.$store.commit('user/login')
  1. // router/index.js
  2. store.state.user.isLogin

mapState()/mapMutation()/mapAction() —-通过映射使访问名称简短

通过这些映射方法可以让大家少敲几个字,避免对$store直接访问。减少耦合

通过映射方法,可以映射到当前组件的实例上。加上一些方法,或加上一些计算属性

state相关修改,Login.vue

  1. import { mapState } from 'vuex'
  2. computed: {
  3. ...mapState('user', ['isLogin']) // 这个调用写成isLogin
  4. ...mapState(['user/isLogin']) // 这个写法也可以,但是调用不方便,调用需要写成这样user/isLogin
  5. // mapState返回的是一个对象,是一个键值对的形式,所以需要进行展开
  6. }
  1. <button @click="login" v-if="!isLogin">登录</button>

action相关修改

  1. import { mapActions } from 'vuex'
  2. methods: {
  3. login () {
  4. // this.$store.dispatch('user/getUserInfo', 'admin')
  5. this['user/login']('admin').then(...)
  6. },
  7. ...mapActions(['user/login', 'user/logout'])
  8. // ...mapActions('user', ['login', 'logout']) 因为和computed中命名冲突,则使用上面的写法
  9. },

Getter

getters是派生状态。一旦和派生状态相关的状态发生改变,则派生的状态也会跟着发生变化(其实是计算属性在vuex中的迁移/实现)

可以使用getters从store的state中派生出一些状态

  1. export default {
  2. namespaced: true,
  3. state: {
  4. isLogin: false,
  5. username: '' // 用户名
  6. },
  7. mutations: {
  8. setUsername (state, username) {
  9. state.username = username
  10. }
  11. },
  12. getters: { // 派生出欢迎信息
  13. welcome: state => {
  14. return state.username + ',欢迎回来';
  15. }
  16. },
  17. actions: {
  18. login ({ commit }, username) {
  19. return new Promise((resolve, reject) => {
  20. setTimeout(() => {
  21. if (username === 'admin') {
  22. // 登录成功,设置用户名
  23. commit('setUsername', username)
  24. resolve()
  25. } else {
  26. reject()
  27. }
  28. }, 1000);
  29. })
  30. }
  31. },
  32. }
  1. {{welcome}}
  2. import { mapGetters, mapState } from 'vuex'
  3. computed: {
  4. ...mapState('user', ['isLogin']),
  5. ...mapGetters('user', ['welcome'])
  6. }

严格模式

严格模式是为了防止用户不通过vuex提供的方式改状态

严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有
的状态变更都能被调试工具跟踪到。开启严格模式strict: true

  1. const store = new Vuex.Store({
  2. // ...
  3. strict: true
  4. })

插件

vuex插件的使用场景:就是在编写和状态无关的操作代码的时候使用。如果都写在vuex中则,页面代码难以维护

Vuex 的 store 接受 plugins 选项,这个选项暴露出每次 mutation 的钩子。Vuex 插件就是一个函
数,它接收 store 作为唯一参数:

  1. // 插件 store实例
  2. const myPlugin = store => {
  3. // 当 store 初始化后调用
  4. }

注册插件:

  1. const store = new Vuex.Store({
  2. // ...
  3. // 注册插件,可以注册多个
  4. plugins: [myPlugin]
  5. })

范例:实现登录状态持久化,store/plugins/persist.js

  1. // vuex的插件
  2. // 存储vuex的值
  3. export default store => {
  4. // 在store初始化的时候,将存储在localStorage中的状态还原
  5. // 初始化时从localStorage获取数据
  6. if (localStorage) {
  7. const user = JSON.parse(localStorage.getItem('user'))
  8. if(user) {
  9. store.commit('user/login');
  10. store.commit('user/setUsername', user.userName);
  11. }
  12. }
  13. // 如果用户的状态发生变化,则自动存入localStorage
  14. // 这个API可以订阅mutation的变化,只要发生mutation,就会执行下面的回调函数
  15. // 用户状态发生变化时缓存之(订阅所有的mutation,只要mutation更改,就会监听到)
  16. store.subscribe((mutation, state) => {
  17. // type的类型
  18. // {type: 'user/login'}
  19. // {type: 'user/logout'}
  20. // {type: cart/card}
  21. if (mutation.type.startsWith('user/login')) {
  22. // 思考题目:因为路由是在登陆之后动态添加的,所以这个会导致需要能访问的路由没有访问到
  23. // 存入localStorage
  24. localStorage.setItem('user', JSON.stringify(state.user))
  25. }else if(mutation.type === 'user/logout') {
  26. // 注销的处理
  27. localStorage.removeItem('user')
  28. }
  29. })
  30. }