Vue从零开始之Vuex

本文详细介绍了如何在Vue项目中使用Vuex进行状态管理,包括安装、登录状态管理、Vuex模块化、持久化数据等。通过实例展示了Vuex的state、getters、mutations、actions的使用,以及解决浏览器刷新后数据丢失的问题。
摘要由CSDN通过智能技术生成

Vuex 状态管理

Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex就是响应式的管理组件间共享的数据(如token,用户名称,位置信息等等).

安装并导入

Vuex 依赖 Promise

//在项目根目录执行如下命令来安装 Vuex
npm install vuex --save --registry=https://registry.npm.taobao.org

//修改 main.js 文件,导入 Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);

登录状态管理

我们利用路由钩子 beforeEach 来判断用户是否登录,期间会用到 sessionStorage (浏览器存储方式的一种).

sessionStorage的方法

//往sessionStorage中设置键值对, 值只能为字符串,可用JSON.stringify()转成json字符串.
sessionStorage.setItem('isLogin', 'true');
//从sessionStorage中获取某个键值对的值
let isLogin = sessionStorage.getItem('isLogin');
//清空sessionStorage
sessionStorage.clear();

添加状态判断用户是否登录

在表单验证成功方法内添加

// 设置用户登录成功
sessionStorage.setItem('isLogin', 'true');

在main.js中利用路由钩子 beforeEach 方法判断用户是否成功登录

// 在每个路由跳转前执行,router为import导入的路由配置的名字
router.beforeEach((to, form, next) => {
  // 获取用户登录状态  let块级变量
  let isLogin = sessionStorage.getItem('isLogin');
  // 注销
  if (to.path == '/logout') {
    // 清空
    sessionStorage.clear();
    // 跳转到登录
    next({path: '/login'});
  }
  // 如果请求的是登录页
  else if (to.path == '/login') {
    if (isLogin != null) {
      // 跳转到首页
      next({path: '/main'});
    }
  }
  // 如果为非登录状态
  else if (isLogin == null) {
    // 跳转到登录页
    next({path: '/login'});
  }
  // 下一个路由
  next();
});

配置 Vuex

创建 Vuex 配置文件在 src 目录下创建一个名为 store 的目录并新建一个名为 index.js 文件用来配置 Vuex,代码如下:

Store的属性:

  • state: 存放共享的数据
    • 虽然也可以直接获取和改变state的值,但官方不推荐这样做.因为直接修改时无法使用Devtools浏览器插件进行监控管理.
  • getters: 用来获取并计算state的值,和计算属性一样缓存计算好的值
  • mutations: 定义改变state值的函数,但不要写异步的代码
    • mutations中使用异步代码修改的state数据,Devtools工具无法监控管理
  • actions: 用于异步执行mutations中的函数
  • module: 模块化
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);

// 全局 state 对象,用于保存所有组件的公共数据
const state = {
  // 定义一个 user 对象
  // 在组件中是通过 this.$store.state.user 来获取
  user: {
    username: '',
    age: 10
  }
};

// 实时监听 state 值的最新状态,注意这里的 getters 可以理解为计算属性
const getters = {
  // 在组件中是通过 this.$store.getters.getUser 来获取
  getUser(state) {
    return state.user;
  }
  // getters对象中获取 getters对象
  getUserSum(state,getters) {
    return getters.getUser.length
  }
  // 接收调用时传入的参数,不能直接写在方法()中
    // $store.getters.moreAgeStu(8)
  moreAgeStu(state){
     // 只能通过return 一个函数来接收参数
      return function(age){
          return state.user.filter(s => s.age > age)
      }
  }
};

// 定义改变 state 初始值的方法,这里是唯一可以改变 state 的地方,缺点是只能同步执行的代码
const mutations = {
  // 在组件中是通过 this.$store.commit('updateUser', user); 方法来提交 mutations
  updateUser(state, user) {
    state.user = user;
  }
  
    // 特殊的提交方式
    this.$store.commit({
        type: 'updateUser',
        user
    })
    updateUser(state, payload) {
        // payload是提交的整个对象包含type和user
    }
};

// 定义触发 mutations 里函数的方法,可以异步执行 mutations 里的函数
const actions = {
  // 在组件中是通过 this.$store.dispatch('asyncUpdateUser', user); 来调用 actions
  asyncUpdateUser(context, user) {
    //通过 context.state 和 context.getters 来获取 state 和 getters。(但不推荐在actions中操作 state)
    //通过 context.commit() 执行mutations中的函数
    context.commit('updateUser', user);
  }
};
// Store 注意 S 大写
export default new Vuex.Store({
  state,
  getters,
  mutations,
  actions
});

挂载Vuex,修改 main.js 增加刚才配置的 store/index.js,关键代码如下:

import store from './store'
new Vue({
  el: '#app',
  store
});

存储用户信息到vuex

在用户登录后将信息添加到state中

this.$store.dispathc('asyncUpdateUser', user);

获取用户信息并显示

{{this.$store.getters.getUser.username}}

state数据响应式渲染

在state中直接初始化好的数据默认就会响应式更新

// 非响应式的 添加/删除
对象['属性']='值' //对象添加属性
delete 对象.属性 //删除对象指定属性

// 响应式的 添加/删除
Vue.set(要修改的数组,索引值,修改后的值)
Vue.set(对象,'属性',值)
Vue.delete(对象,属性)

mapState 辅助函数

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性

// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // 箭头函数可使代码更简练
    count: state => state.count,

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

对象展开运算符

mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性

computed: {
  localComputed () { /* ... */ },
  // 使用对象展开运算符... 将此对象混入到外部对象中
  ...mapState({
    // ...
  })
}

mutations使用常量作为函数名

防止调用时因为写错字母导致出错

// changliang.js
export const INCREMENT = 'increment'

// /store/index.js Vuex配置文件
import {INCREMENT} from '../changliang.js';
const mutations = {
  // 使用变量作为方法名
  [INCREMENT](state) {
  }
};

// 调用页面
import {INCREMENT} from '../changliang.js';
$store.commit(INCREMENT)

调用actions异步操作 执行成功/失败时回调

  • 普通方式
this.$store.dispatch('asyncUpdateUser',{
    message: {
        name: 'zhangsan'
    },
    success: ()=>{
        console.log('执行完毕手动回调')
    }
})


const actions = {
  asyncUpdateUser(context, payload) {
      setTimeout(()=>{
         context.commit('updateUser', payload.message.name); 
      },1000)
      payload.success();  
  }
};
  • Promise 方式
const actions = {
  asyncUpdateUser(context, payload) {
     return new Promise((resolve,reject) => {
          setTimeout(()=>{
             context.commit('updateUser', payload); 
             // 执行调用者的.then()
             resolve('执行完成')
          },1000)
     }) 
  }
};

this.$store.dispatch('asyncUpdateUser','携带信息')
    .then(res => {
        console.log(res);
    })

浏览器刷新 Vuex 数据消失

手动解决

问题描述

Vuex 的状态存储是响应式的,当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。但是有一个问题就是:vuex 的存储的数据只是在页面的中,相当于我们定义的全局变量,刷新之后,里边的数据就会恢复到初始化状态。但是这个情况有时候并不是我们所希望的。

解决方案

监听页面是否刷新,如果页面刷新了,将 state 对象存入到 sessionStorage 中。页面打开之后,判断 sessionStorage 中是否存在 state 对象,如果存在,则说明页面是被刷新过的,将 sessionStorage 中存的数据取出来给 vuex 中的 state 赋值。如果不存在,说明是第一次打开,则取 vuex 中定义的 state 初始值。

修改代码

在 App.vue 中增加监听刷新事件

  export default {
    name: 'App',
    mounted() {
      window.addEventListener('unload', this.saveState);
    },
    methods: {
      saveState() {
        sessionStorage.setItem('state', JSON.stringify(this.$store.state));
      }
    }
  }

修改 store/index.js 中的 state

const state = sessionStorage.getItem('state') ? JSON.parse(sessionStorage.getItem('state')) : {
  user: {
    username: ''
  }
};

使用vuex-persistedstate持久化插件

安装

npm install vuex-persistedstate
  • 使用localStorage来固化数据(默认)
import persistedState from 'vuex-persistedstate'
export default new Vuex.Store({
    // ...
    plugins: [persistedState()]
})
  • 使用sessionStorage来固化数据
plugins: [
    persistedState({ storage: window.sessionStorage })
]
  • 使用cookie的情况

先安装npm install js-cookie

import persistedState from 'vuex-persistedstate'
import * as Cookies from 'js-cookie'

export default new Vuex.Store({
  // ...
  plugins: [
    persistedState({
      storage: {    
        getItem: key => Cookies.get(key),
        setItem: (key, value) => Cookies.set(key, value, { expires: 7 }),
        removeItem: key => Cookies.remove(key)
      }
    })
  ]
})

Vuex模块化

由于使用单一状态树(所有状态都存放在一个state对象中),应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

创建 user 模块

在 store 目录下创建一个名为 modules 的目录并创建一个名为 user.js 的文件,代码如下:

//创建一个user模块,模块下有vuex的四个属性
const User = {
  state: sessionStorage.getItem('userState') ? JSON.parse(sessionStorage.getItem('userState')) : {
    user: {
      username: ''
    }
  },
  getters: {
    getUser(state) {
      return state.user;
    },
    // 模块中 getters中的函数可以获取到根模块的state
    getUser(state, getters, rootState) {
      return state.user;
    }
  },
  mutations: {
    updateUser(state, user) {
      state.user = user;
    }
  },
  // 模块内的actions只能调用本模块的 mutations
  actions: {
    asyncUpdateUser(context, user) {
      //对于模块内部的 action,局部状态通过 context.state 获取,根节点状态通过 context.rootState 获取. getters同理
      context.commit('updateUser', user);
    }
  }
};
export default User;

默认情况下除了state其他三个属性的调用方式不变,不用指定使用哪个模块.

修改 store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import User from './modules/user'
Vue.use(Vuex);
export default new Vuex.Store({
  modules: {
    // 组件中使用this.$store.state.User.user获取到User模块的state状态的user属性
        // modules中定义的模块对象最终会被加载到store的state中
    User
  }
});

命名空间

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的.

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。

const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true,

      // 模块内容(module assets)
      state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      }
    }
  }
})

Vuex推荐目录结构

── store
    ├── index.js          # 我们组装模块并导出 store 的地方
    ├── actions.js        # 根级别的 action
    ├── getters.js        # 根级别的 getters
    ├── mutations.js      # 根级别的 mutation
    └── modules
        ├── cart.js       # 购物车模块
        └── products.js   # 产品模块
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值