Vuex初识
Vuex的介绍
Vuex是一个专为Vue.js应用程序开发的状态管理模式。官网给出Vuex的流程图如下:
这个流程图我的解读如下:
- Components: 页面中的组件
- Actions: 页面中要改变数据状态的动作
- Mutations: vuex中执行数据状态更改的行为
- State: 数据仓库(由vuex来管理状态的数据组成集合)
我对Vuex这几个模块的解读如下图所示:
组件(Components)只做页面的展示,数据全部交给vuex来处理。可以将vuex的state看成前端自己的持久层—数据仓库。Actions/Mutations监测到组件中执行了对数据(如:msg)进行更改的动作后,去数据仓库State中保存的数据(msg)进行相应的更改操作。而组件展示数据(msg),均是通过Getters从数据仓库State中取出相应的数据(msg)。
结合案例说明如下:
项目的完整源码在我的GitHub项目—learn-vuex中,在git的命令行工具运行如下命令,可以将该项目克隆到本地。
# 在git命令行工具中执行,克隆到本地
git clone https://github.com/zhenye163/learn-vuex
# 加载依赖包
npm install
# 运行该项目
npm run dev
搭建Vue脚手架
搭建Vue脚手架的准备工作详见: 欢迎入坑
# 生成名称为learn-vuex的项目
vue init webpack learn-vuex
cd learn-vuex
# 拉取国内淘宝镜像,安装脚手架需要的依赖环境
npm install --registry=https://registry.npm.taobao.org
# 启动项目
npm run dev
生成的项目结构说明如下:
项目启动后,访问http://localhost:8080/#/,如果出现如下页面,说明vue脚手架搭建成功。
模拟的场景阐明
脚手架已经搭建成功,接下来我们就可以开始折腾了。为了能够理解Vuex到底给Vue带来了什么好处,我想用本项目模拟一个点餐吃饭(呃,一不小心就暴露了我这吃货本性)的场景。
场景一 :在家吃饭
场景二:在餐馆吃饭
场景再现的准备工作
- 将项目的目录结构调整如下:
- src
- assets
- logo.png
- components
- HelloWorld.vue
- router
- index.js
- store
- actions.js
- getters.js
- index.js
- mutations.js
- rootState.js
- views
- family.vue
- restaurant.vue
- App.vue
- main.js
- 在src/router/index.js中配置路由如下
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Family from '@/views/family'
import Restaurant from '@/views/Restaurant'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
{
path: '/family',
name: 'Family',
component: Family
},
{
path: '/restaurant',
name: 'Restaurant',
component: Restaurant
}
]
})
- 在src/main.js中导入Element-UI
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
Vue.use(ElementUI)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
不用Vuex的场景再现—在家吃饭
- 直接在src/views/family.vue中实现
<template>
<div>
在家吃饭
<div>
<br/><br/>
<div>
<div>此时桌子上的菜有:</div>
<br/><br/>
<div v-for="food in foodList" :key="food.id">
<div>菜名:{{food.name}} ,价格:{{food.price}}</div>
</div>
</div>
<br/><br/>
<el-button @click.native.once="addFood()">加个炖排骨</el-button>
</div>
</div>
</template>
<script>
export default {
data () {
return {
foodList: [],
menu: []
}
},
created () {
this.initData()
},
methods: {
initData () {
this.foodList = [
{id: 1, name: '酸辣土豆丝', price: 12},
{id: 2, name: '手撕包菜', price: 12}
]
this.menu = [
{id: 1, name: '酸辣土豆丝', price: 12},
{id: 2, name: '手撕包菜', price: 12},
{id: 3, name: '炖排骨', price: 48},
{id: 4, name: '麻婆豆腐', price: 12},
{id: 5, name: '武昌鱼', price: 12}
]
},
addFood () {
this.foodList.push(this.menu[2])
}
}
}
</script>
<style>
</style>
- 访问localhost:8080/#/family,可看到如下页面:
点击点单按钮可以看到相应的点菜效果。
用Vuex后的场景再现—在餐馆吃饭
- 首先在src/main.js中引入vuex的数据仓库store
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import store from './store/index'
Vue.use(ElementUI)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
- 在src/views/restaurant.vue中定义页面内容如下:
<template>
<div>
在餐馆吃饭
<div>
<br/><br/>
<div>
<div>此时桌子上的菜有:</div>
<br/><br/>
<div v-for="food in foodList" :key="food.id">
<div>菜名:{{food.name}} ,价格:{{food.price}}</div>
</div>
</div>
<br/><br/>
<el-button @click.native.once="addFood('炖排骨')">加个炖排骨</el-button>
</div>
</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
data () {
return {}
},
computed: {
...mapGetters(['foodList'])
},
methods: {
addFood(food){
this.$store.dispatch('addFood', '炖排骨')
}
}
}
</script>
<style scoped>
</style>
页面中需要展示的数据foodList是从state中通过getters获取
- 在src/store/rootState.js中定义需要vuex进行管理的数据如下
const state = {
// 点单即将上菜的食物
food: {id: -1, name: '', price: -1},
// 饭桌上已经有哪些食物
foodList: [
{id: 0, name: '酸辣土豆丝', price: 12},
{id: 1, name: '手撕包菜', price: 12}
]
}
export default state
- 在src/store/getters.js中定义如何获取state中的数据
export const foodList = state => state.foodList
export const menuList = state => state.menuList
- 页面中有点单(炖排骨)的行为,由src/store/action.js检测到
export const addFood = ({commit}, payload) => {
commit({
type: 'cookFood',
// payload告诉我们点的菜是什么
msg: payload
})
}
- 点菜后,让后厨做菜上菜(src/store/mutations.js)
export const cookFood = (state, payload) => {
state.food.id = state.foodList.length
state.food.name = payload.msg
// 菜的价格由餐馆决定为28
state.food.price = 28
// 菜做好了,push方法进行上菜
state.foodList.push(state.food)
// 方法执行完,需要将state中的food清零
state.food = {id: -1, name: '', price: -1}
}
- 将vuex的所有部分汇总到(src/store/index.js)中,然后页面就会知道菜(炖排骨)已经做好并给顾客上菜
import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import * as mutations from './mutations'
import state from './rootState'
Vue.use(Vuex)
const store = new Vuex.Store({
actions,
getters,
mutations,
state
})
export default store
- 项目启动需要先导入vuex依赖包
# 导入vuex需要的依赖包
npm install --save vuex
# 启动项目
npm run dev
- 访问http://localhost:8080/#/restaurant,可看到如下页面:
通过浏览器的返回/前进,多次测试发现以下几个现象:
- 每次进入/family页面,炖排骨这道菜就消失了。
- 如果没有强制刷新页面(F5),每次进入/restaurant页面,点的炖排骨这道菜还在。
- 强制刷新(F5),进入/restaurant页面,炖排骨这道菜也消失了。
总结:
vuex在前台进行了数据的持久化处理,即保存了foodList中的值。这样是更符合实际情况的。总不能我点菜后,菜已经上桌了,我回家拿个手机,再回到餐馆发现桌上的菜(炖排骨)就不见了!!!。而强制刷新页面(F5),是将所有的组件摧毁然后再次重启。也就是告诉餐馆:“前面点的菜不算,我重新点。”这样的话,餐馆是不会多给你加菜(炖排骨的)。它是不可能做赔本买卖!!!