状态管理可以简单理解为vue中的某些全局的data属性。 当组件状态增多时,整个应用和状态分散在每个组件和实例中。部分还会出现状态共享。这时最好的方案就是vuex。
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
安装vuex:
// vue add vuex
npm install vuex --save
在src下面新建一个store文件夹:里面有index.js
// vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state:{
//存放状态
},
mutations:{
//修改状态
},
actions:{
}
})
对于业务来说,需要把isLogin
。
Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
单状态树和模块化并不冲突——在后面的章节里我们会讨论如何将状态和状态变更事件分布到各个子模块中。
//引入store
import store from '../store'
data () {
return {
count:store.state.isLogin,
}
},
这种获取方式有点低效了,有没有更好的方式呢?
在main.js中:
import store from './store'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
// 把 store 象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
store,
router,
components: { App },
template: `<App/>`
})
在组件中就能通过this.$store.isLogin
访问到了。
还可以进行各种骚操作,比如说,登录后显示“欢迎回来”
<router-link v-if="!$store.state.isLogin" to='/login'>login</router-link>
<span v-else>welcome back</span>
每当创建一个状态,就一定有一个方法去修改它的状态,称之为mutations
。如果需要修改store中的值唯一的方法就是提交mutation来修改.
Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
const store = new Vuex.Store({
state: {
isLogin:false
},
mutations: {
login (state) {
// 变更状态
state.isLogin=true;
}
}
})
你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法:
// login.vue
login() {
this.$store.commit("login");
const redirect = this.$route.query.redirect;
this.$router.push({ path: redirect });
// console.log(this.$router)
}
登录实际上是一个异步操作。 action并不能更新状态,只能提交状态更新事件。 而异步操作都应该是在action中来派发的。
// 在action中也定义一个loginAction方法。接收一个上下文:context
actions:{
loginAction(context){
console.log(context);
window.setTimeout(()=>{
context.commit('login')
},1000)
}
}
登录时,通过action派发一个状态更新
// login.vue
login() {
// this.$store.commit("login");
this.$store.dispatch(`loginAction`)
const redirect = this.$route.query.redirect;
this.$router.push({ path: redirect });
}
}
打印上下文context
,发现这个就是store本身。
现在等1秒之后,成功了。但是应该返回外界操作成功还是失败。所以应当return一个promise对象。
actions:{
loginAction(context){
//console.log(context);
return new Promise(resolve=>{
window.setTimeout(()=>{
context.commit('login');
resolve(true)
},1000);
})
}
}
loginAction成功之后返回一个resolve,因此可以then做后续判断:
// login.vue
login() {
this.$store.dispatch('loginAction').then(isLogin=>{
if(isLogin){
const redirect = this.$route.query.redirect||'/';
this.$router.push({ path: redirect });
}
})
}
}
那么登录的功能已经完成了,但是还可以给点安慰剂比入,登录之后不让点击按钮:
// login.vue
<template>
<div>
<h1>this is login page</h1>
<button :disabled="loading" @click="login">{{loading?'signing...':'login'}}</button>
</div>
</template>
<script>
export default {
data() {
return {
loading: false
}
},
methods: {
login() {
this.loading=true;
this.$store.dispatch('loginAction').then(isLogin=>{
this.loading=false;
const redirect = this.$route.query.redirect||'/';
this.$router.push({ path: redirect });
})
}
}
};
</script>
也可以传参数。 甚至可以做各种复杂业务逻辑
上面的代码,已经有点乱了。比如说,isLogin能否更加简单?
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:
// 在单独构建的版本中辅助函数为 Vuex.mapState
import {mapState} from 'vuex'
export default {
computed: mapState(['isLogin']),
data() {...}
}
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
你可以愉快的用this.isLogin
来指代this.$store.state.isLogin
了。
mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符(现处于 ECMAScript 提案 stage-4 阶段),我们可以极大地简化写法:
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。
当你引入mapActions,也可以用
methods: {
...mapActions['loginActions'],
login() {
this.loading=true;
this.loginAcions.then(isLogin=>{
this.loading=false;
const redirect = this.$route.query.redirect||'/';
this.$router.push({ path: redirect });
})
}
}
登录时需要传参,你可以
this.loginAcions({username:'dangjingtao',password:'123456'}).then(...
接收函数时:
loginAction(context,payloads){
// payloads就是你的参数。
}
// main.js
export default new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
Getter相当于vue中的computed计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算,这里我们可以通过定义vuex的Getter来获取,Getters 可以用于监听、state中的值的变化,返回计算后的结果,这里我们修改Hello World.vue文件如下:
data () {
return {
totdodone:this.$store.getters.doneTodos
}
},
也是相当地简单。
页面多时,还需要对不同页面的对应状态和方法做模块化。
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
import Cart from './cart';
Vue.use(Vuex)
// Cart={...}
export default new Vuex.Store({
modules:{
Cart,
},
// ..