Vuex-一个专为 Vue.js 应用程序开发的状态管理模式
为什么会出现Vuex
非父子关系的组件如何进行通信?(Event Bus)
bus.js
import Vue from 'vue';
export default new Vue();
foo.vue
import bus from './bus.js';
export default {
methods: {
changeBroData() {
bus.$emit('changeBarData');
}
}
}
bar.vue
import bus from './bus.js';
export default {
created() {
bus.$on('changeBarData',() => {
this.count++;
});
}
}
但是当我们需要修改这个操作的时候,我们需要动3个地方,倘若项目小的话还倒好说,但是对于大项目组件间交互很多的概况,Event Bus就会表现的很吃力。Vuex的出现就是为了解决这一状况。
Vuex介绍
安装:
script标签引入https://unpkg.com/vuex
NPMnpm install vuex --save-dev
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
- Vuex的状态存储是响应式的。当Vuex的状态属性发生变化时,相应的组件也会更新。
- 无法直接修改Vuex的状态。只能通过显式的提交(commit)。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increase(state) {
state.count++;
}
}
});
store.commit('increase');
console.log(store.state.count); // 1
State
从上文我们知道Vuex是响应式的,我们如何在Vue实例中使用Vuex中的实例呢,自然离不开计算属性computed了。
//仓库
const store = new Vuex.Store({
state: {
count: 1
}
});
//foo组件
const foo = {
template: `
<div>
{{ count }}
<div>
`,
computed: {
count: () => store.state.count
}
};
这时候问题就来了,如果这样进行引入store的话,组件就会以来全局状态单例。
Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)):
//挂载App
new Vue({
el: '#app',
//注入store选项
store,
render: h => h(App)
});
//foo组件
const foo = {
template: `
<div>
{{ count }}
<div>
`,
computed: {
count() {
// 不能使用箭头函数,不然this就不是Vue实例了
// 通过this.$store获取到store实例
return this.$store.state.count
}
}
};
如果有很多状态需要映射,我们岂不是要写好多代码,这时候需要用到mapState辅助函数,使用 mapState 辅助函数帮助我们生成计算属性。
辅助函数 实例1
const foo = {
template: `
<div>
count: {{ count }}
countAlias: {{ countAlias }}
countPlusLocalCount: {{ countPlusLocalCount }}
<div>
`,
data() {
return {
localCount: 2
};
},
computed: Vuex.mapState({
//只处理仓库中的count
count: state => state.count*2,
//映射
countAlias: 'count',
//需要用到Vue实例的话不能使用箭头函数不然this无法获取到Vue实例
countPlusLocalCount(state) {
return state.count + this.localCount;
}
})
};
当然当映射名与state中属性名相同时,可以通过mapState(['count'])
这种形式书写。
因为组件本身也有许多计算属性直接使用mapState的话无法扩充computed了,这时候我们可以使用ES新属性: 对象展开运算符
computed: {
localComputed() {
return this.count;
},
...Vuex.mapState({
//只处理仓库中的count
count: state => state.count*2,
//映射
countAlias: 'count',
//需要用到Vue实例的话不能使用箭头函数不然this无法获取到Vue实例
countPlusLocalCount(state) {
return state.count + this.localCount;
}
})
}
ATT:使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。
Getter
有时候我们需要过滤/处理state中的属性值,就像Vue实例中我们需要处理data数据一样,Vue有computed。Vuex同样提供给我们一个属性getter,getter就是Vuex的“计算属性”。
Getter接收state作为第一个参数
//仓库
const store = new Vuex.Store({
state: {
users: [{
name: 'jason',
id: 1,
female: false
}, {
name: 'molly',
id: 2,
female: true
}, {
name: 'steven',
id: 3,
female: false
}]
},
getters: {
// 过滤所有属性中female是true的对象
getFemaleUsers: state => state.users.filter(user => user.female)
}
});
console.log(store.getters.getFemaleUsers);
//[{ "name": "molly", "id": 2, "female": true }]
当然Getter同样有辅助函数 mapGetters将 store 中的 getter 映射到局部计算属性
直接上对象展开运算符了
辅助函数 实例1
//foo组件
const foo = {
template: `
<div>
femaleList: {{ femaleList }}
<div>
`,
computed: {
// 属性名相同的不再阐述
...Vuex.mapGetters({
femaleList: 'getFemaleUsers'
})
}
};
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
提交时额外的参数被称为载荷
普通风格提交方式
store.commit('mutationName', {
params: '参数'
});
对象风格提交方式
store.commit({
type: 'mutationName',
params: '参数'
});
注意点:
- 设置对象新属性时
方法1:Vue.set(obj, 'newVal', 100)
方法2:obj = {...obj, newVal: 100};
Mutations的事件类型尽量使用常量,并用一个文件进行维护。方便协同开发以及维护。
// 存储事件名mutations-types.js
export const MUTATIONS_GETDATA = 'MUTATIONS_GETDATA';
export const MUTATIONS_SUCCESS = 'MUTATIONS_SUCCESS';
import { MUTATIONS_GETDATA ,MUTATIONS_SUCCESS } from 'path/mutations-types.js'; const store = new Vuex.Store({
mutations: {
[MUTATIONS_GETDATA]() {
// todo
},
[MUTATIONS_SUCCESS]() {
// todo
}
}
});- Mutations中的函数必须都是同步函数,异步函数都要写在Actions中。
在组件中提交Mutation$store.commit('xxx','yyy')
当然也有对应的辅助函数帮助我们映射到对应的methods方法中
import { mapMutations } from 'vuex';
export default {
methods: {
...mapMutations([
'event1',
'event2'
]),
...mapMutations({
eventAlias: 'event3' //将this.eventAlias()映射为this.$store.commit('event3')
})
}
};
Action
Action与Mutation的不同在于Action不直接修改状态只是做commit一个mutation、然后就是可以做异步操作。
简单的Action
const INCREASE_COUNT = 'INCREASE_COUNT';
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
[INCREASE_COUNT](state) {
state.count++;
}
},
actions: {
add({ commit }) {
commit(INCREASE_COUNT);
}
}
});
看到这我们或许以为Actions的作用与Mutations不一样么,对到现在为止作用是一样的,但是Actions可以执行异步操作
const INCREASE_COUNT = 'INCREASE_COUNT';
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
[INCREASE_COUNT](state) {
state.count++;
}
},
actions: {
add({ commit }) {
setTimeout(() => {
commit(INCREASE_COUNT);
}, 1000);
}
}
});
Action的分发也有两种方式
普通风格分发方式
store.dispatch('actionName', {
params: '参数'
});
对象风格分发方式
store.dispatch({
type: 'actionName',
params: '参数'
});
辅助函数mapActions用法与mapMutations无异
代码链接
methods: {
...mapActions({
add: 'add'
})
}
组合Action
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
},
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
store.dispatch('actionA').then(() => {
// ...
})
或者
store.dispatch('actionB');
Module
如果项目庞大的话我们需要维护很多状态,这时候store会变得非常庞大,我们就需要store分割成很多模块(Module),每个模块同样拥有自己的state,getters,mutations,actions以及modules
const moduleA = {
state: {
a: 'A'
}
};
const moduleB = {
state: {
a: 'B'
}
};
const store = new Vuex.Store({
modules: {
moduleA,
moduleB
}
});
console.log(store.state.moduleA.a); //A
console.log(store.state.moduleB.a); //B
模块内部的mutation与getter接收的第一个参数都是局部状态对象
例如
const moduleA = {
state: {
price: 50,
count: 2
},
getters: {
totalPrice: state => state.price*state.count
},
mutations: {
decreaseCount(state) {
state.count--;
}
}
};
模块内部的getter,根节点的状态会在第三个参数展示出来。
注:根节点的状态就是指的store.state
const moduleB = {
state: {
count: 2
},
getters: {
sum: (state, getters, rootState) => state.count+rootState.count
}
};
const store = new Vuex.Store({
state: {
count: 1
},
modules: {
moduleB
}
});
console.log(store.getters.sum);// 3
模块内部的action,局部状态:
context.state
根部状态:context.rootState
const moduleC = {
state: {
count: 2
},
mutations: {
increaseCount(state) {
state.count++;
}
},
actions: {
addCount({ commit, state, rootState }) {
let sum = state.count + rootState.count;
if(sum % 3 === 0) {
commit('increaseCount');
}
}
}
};
const store = new Vuex.Store({
state: {
count: 1
},
modules: {
moduleC
}
});
console.log(store.state.moduleC.count);// 2
store.dispatch('addCount');
console.log(store.state.moduleC.count);// 3
命名空间
默认情况下,模块内部的 action
、mutation
和 getter
是注册在全局命名空间的——这样使得多个模块能够对同一 mutation
或 action
作出响应。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true
的方式使其成为命名空间模块。当模块被注册后,它的所有 getter
、action
及 mutation
都会自动根据模块注册的路径调整命名。(摘自官网)
注1:特别提出:模块内的state是嵌套的,namespaced
属性不会对其产生丝毫影响。
注2:没加namespaced: true
的模块会继承父模块的命名空间
代码链接
const store = new Vuex.Store({
modules: {
moduleA: {
namespaced: true,
getters: {
count: () => 0 // store.getters['moduleA/count']
},
modules: {
moduleB: {
getters: {
count1: () => 1 //继承了父模块的命名空间 store.getters['moduleA/count1']
}
},
moduleC: {
namespaced: true,
getters: {
count2: () => 2 //继承了父模块的命名空间 store.getters['moduleA/moduleC/count1']
}
}
}
}
}
});
console.log(store.getters['moduleA/count']);// 0
console.log(store.getters['moduleA/count1']);// 1
console.log(store.getters['moduleA/moduleC/count2']);// 2
在命名空间模块内部访问全局内容
模块内部希望使用全局state
与全局getter
rootState
与rootGetters
会作为getter
第三个第四个参数传入如
someGetter: (state, getters, rootState, rootGetters) => {
//todo
}
const store = new Vuex.Store({
getters: {
someOtherGetter: state => 'ROOT'
},
modules: {
moduleA: {
namespaced: true,
getters: {
someGetter(state,getters,rootState,rootGetters) {
return getters.someOtherGetter; // INSIDE
// return rootGetters.someOtherGetter; // ROOT
},
someOtherGetter: state => "INSIDE"
}
}
}
});
console.log(store.getters['moduleA/someGetter']);
也会通过
context.rootState
与context.rootGetts
传入
如
someAction:(context) => {
//todo
}
或者
someAction:({ commit, dispatch, rootState, rootGetters }) => {
//todo
}
模块内部希望使用全局mutation
与全局action
,只需要在执行分发或者提交的时候在第三个参数位置传入{ root: true }
如
dispatch('someOtherAction', null, {root: true});
commit('someOtherMutation', null, {root: true});
const store = new Vuex.Store({
getters: {
someOtherGetter: state => 'ROOT'
},
actions: {
someOtherAction() {
console.log('ROOT_ACTION');
}
},
modules: {
moduleA: {
namespaced: true,
getters: {
someGetter(state,getters,rootState,rootGetters) {
return getters.someOtherGetter; // INSIDE
// return rootGetters.someOtherGetter; // ROOT
},
someOtherGetter: state => "INSIDE"
},
actions: {
someAction({ dispatch, getters, rootGetters }) {
console.log(getters.someOtherGetter);//INSIDE
console.log(rootGetters.someOtherGetter);//ROOT
dispatch('someOtherAction');//INSIDE_ACTION
dispatch('someOtherAction', null, {root: true});//ROOT_ACTION
},
someOtherAction() {
console.log('INSIDE_ACTION');
}
}
}
}
});
console.log(store.getters['moduleA/someGetter']);
store.dispatch('moduleA/someAction');
当我们将store里的状态映射到组件的时候,会出现下面的问题
computed: {
...mapState({
b1:state => state.moduleA.moduleB.b1,
b2:state => state.moduleA.moduleB.b2
})
},
methods: {
...mapActions({
'moduleA/moduleB/action1',
'moduleA/moduleB/action2'
})
}
是不是比较繁琐,代码太过冗余。
当然mapState
、mapGetters
、mapMutations
、mapActions
这些辅助函数都可以将空间名称字符串作为第一个参数传递,这样上面的例子可以简化成
computed: {
...mapState('moduleA/moduleB', {
b1:state => state.b1,
b2:state => state.b2
})
},
methods: {
...mapActions('moduleA/moduleB', {
'action1',
'action2'
})
}
当然还有一个方法,使用Vuex
提供的createNamespacedHelpers
创建基于某个命名空间函数,它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数。
import { createNamespacedHelpers } from 'vuex';
const { mapState , mapActions } = createNamespacedHelpers('moduleA/moduleB');
export default {
computed: {
...mapState({
b1:state => state.b1,
b2:state => state.b2
})
},
methods: {
...mapActions({
'action1',
'action2'
})
}
}
模块重用
有时我们可能需要创建一个模块的多个实例:
- 创建多个 store,他们公用同一个模块
- 在一个 store 中多次注册同一个模块
如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。
实际上这和 Vue 组件内的 data 是同样的问题。因此解决办法也是相同的——使用一个函数来声明模块状态
const MyReusableModule = {
state () {
return {
foo: 'bar'
}
},
// mutation, action 和 getter 等等...
}
原文地址:https://segmentfault.com/a/1190000012645384
Vuex-一个专为 Vue.js 应用程序开发的状态管理模式的更多相关文章
- Vue.js 2.x笔记:状态管理Vuex(7)
1. Vuex简介与安装 1.1 Vuex简介 Vuex是为vue.js应用程序开发的状态管理模式,解决的问题: ◊ 组件之间的传参,多层嵌套组件之间的传参以及各组件之间耦合度过高问题 ◊ 不同状态中 ...
- Vue.js状态管理模式 Vuex
vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化. 安装.使用 vuex 首先我们在 vue. ...
- 五、vue状态管理模式vuex
一.vuex介绍 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化. 即data中属性同时有一 ...
- 转 理解vuex -- vue的状态管理模式
转自:https://segmentfault.com/a/1190000012015742 vuex是什么? 先引用vuex官网的话: Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 ...
- vuex -- vue的状态管理模式
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化. 状态管理模式.集中式存储管理 一听就很高大 ...
- 理解Vue的状态管理模式Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化. 状态管理模式.集中式存储管理,一听就很高大 ...
- Vue状态管理模式---Vuex
1. Vuex是做什么的? 官方解释: Vuex 是一个专为Vue.js 应用程序开发的 状态管理模式 它采用 集中式存储管理 应用的所有组件的状态, 并以相应的规则保证状态以一种可预测的方式发生变化 ...
- Vuex(一)——vuejs的状态管理模式
一.Vuex是什么? Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式. 它采用集中式存储 管理 应用的所有组件 的 状态,并以 相应的规则 保证 状态以一种 可预测的方式 发生变化. ...
- 理解vuex的状态管理模式架构
理解vuex的状态管理模式架构 一: 什么是vuex?官方解释如下:vuex是一个专为vue.js应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证以一种可预测的 ...
随机推荐
- Java多线程编程模式实战指南(一):Active Object模式--转载
本文由黄文海首次发布在infoq中文站上:http://www.infoq.com/cn/articles/Java-multithreaded-programming-mode-active-obj ...
- mybatis与spring整合配置
mybatis与spring整合配置: 第一种方式:(此处配置扫描的包路径.注解.每个mapper类上面需要加@Repository才能纳入spring的bean管理器中) <!-- 自动扫描m ...
- [ Linux ] user, password, sudoers
- 1. Add Linux user, setup password http://linux.vbird.org/linux_basic/0410accountmanager.php - 2. ...
- linux上测试磁盘IO速度
运维工作,经常要测试服务器硬件性能,以此来判断是否存在性能瓶颈. 下面介绍在linux上测试磁盘IO速度的工具: 1.hdparm CentOS中,安装的两种方法: 1) yum安装. # yum i ...
- watch监听
watch: { getTitle:{ handler:function(val,oldval){ }, deep:true//对象内部的属性监听,也叫深度监听 }, },
- How Javascript works (Javascript工作原理) (六) WebAssembly 对比 JavaScript 及其使用场景
个人总结: 1.webassembly简介:WebAssembly是一种用于开发网络应用的高效,底层的字节码.允许在网络应用中使用除JavaScript的语言以外的语言(比如C,C++,Rust及其他 ...
- bootstrap 因跳页黑色背景无法关闭
只需要在跳页之前加上如下代码: $(".modal-backdrop").remove();
- Ubuntu 如何进入系统文件/etc/profile修改内容
转载:https://blog.csdn.net/cfq1491/article/details/81088117 /etc/profile 默认权限为 -rw-r--r-- 即只有root用户可以修 ...
- 一张图说docker
- 紫书 习题 11-8 UVa 1663 (最大流求二分图最大基数匹配)
很奇怪, 看到网上用的都是匈牙利算法求最大基数匹配 紫书上压根没讲这个算法, 而是用最大流求的. 难道是因为第一个人用匈牙利算法然后其他所有的博客都是看这个博客的吗? 很有可能-- 回归正题. 题目中 ...