VueX
介绍
VueX是适用于在Vue
项目开发时使用的状态管理工具。试想一下,如果在一个项目开发中频繁的使用组件传参的方式来同步data
中的值,一旦项目变得很庞大,管理和维护这些值将是相当棘手的工作。为此,Vue
为这些被多个组件频繁使用的值提供了一个统一管理的工具——VueX
。在具有VueX
的Vue
项目中,我们只需要把这些值定义在VueX
中,即可在整个Vue
项目的组件中使用。
安装
以下步骤的前提是你已经完成了Vue项目构建,并且已转至该项目的文件目录下。
- npm安装VueX
npm i vuex -s
- 在项目的根目录下新增一个store文件夹,在改文件夹内创建index.js
使用
1、初始化store下的index中的内容
import Vue from 'vue';
import Vuex from 'vuex';
// 挂载Vuex
Vue.use(Vuex);
// 创建VueX对象
const store = new Vuex.Store({
state:{
// 存放的键值对就是所要管理的状态
name:'helloVueX',
bumber: 0,
list: [
{id: 1, name: '1111'},
{id: 2, name: '2222'}
]
},
getters: {},
mutations: {},
actions: {},
modules: {}
});
export default store;
2、将store
挂载到当前项目的Vue实例当中去(main.js)
3、在组件中使用Vuex
<template>
<div id="app">
<img src="./images/logo.png" alt="华宇信息">
<h1>前后端分离工程结构</h1>
name:
<h1>{{$store.state.name}}</h1>
</div>
</template>
4、什么时候应该用VueX
呢?
- 这个问题因人而异,如果不需要开发大型的单页应用,此时完全没有必要使用
vuex
,比如页面就两三个,使用vuex
后增加的文件比现在的页面还要多,那就咩这个必要了。 - 假如项目达到了中大型应用的规模,此时会考虑如何更好地在组件外部管理状态,
Vuex
将会成为自然而然的选择。
核心概念
1、State
Vuex
就是一个仓库,仓库里面放了很多对象。其中state
就是数据源存放地,对应于一般Vue
对象里面的data
。state
里面存放的数据是响应式的,Vue
组件中从store
中读取数据,若是store中的数据发生了改变,依赖这个数据的组件也会发生改变。
1.1在Vue组件中获得Vuex状态
官方建议我们以上操作$store.state.XXX
最好放在计算属性中,这样可以让我们的代码看起来更优雅一些,就像这样:
<template>
<div id="app">
name:
<h1>{{getName}}</h1>
</div>
</template>
<script>
export default {
name: 'App',
computed: {
getName() {
return this.$store.state.name;
}
}
};
</script>
每当store.state.name
变化的时候,都会重新求取计算属性,并且更新相关联的DOM。
1.2 mapState辅助函数
当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为解决这个问题,我们可以使用mapState
辅助函数帮组我们生成计算属性。
<template>
<div id="app">
<h1>name: {{name}}</h1>
<h1>nameAlias: {{nameAlias}}</h1>
<h1>countNumber: {{countNumber}}</h1>
</div>
</template>
<script>
import {mapState} from 'vuex';
export default {
name: 'App',
computed: mapState({
name: state => state.name,
// 赋别名,赋别名的话,这里接收对象,而不是数组
nameAlias: 'name',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countNumber(state) {
return state.number + 2;
}
})
};
</script>
1.3 对象展开运算符
mapState
函数返回的是一个对象。我们如何将它与局部计算
<script>
import {mapState} from 'vuex';
export default {
name: 'App',
computed: {
...mapState({
name: 'name',
nameAlias: 'name',
countNumber: function (state) {
return state.number + 2;
}
})
}
};
</script>
或
<script>
import {mapState} from 'vuex';
export default {
name: 'App',
computed: {
...mapState(['name', 'number', 'list'])
}
};
</script>
2、了解修饰器:Getter
设想一个场景,我们已经将store
中的list.name
展示在页面上了,而且是很多页面展示了,此时需求说:“所有的list name前面都要加上 hello”;设施后你第一次想到怎么加呢?在每个页面上,使用this.$store.state.lis
t获取到值之后,进行遍历,前面追加"hello"即可。
- 假如你在A、B、C三个页面都用到了name,那么你要在这A、B、C三个页面都修改一遍,多个页面你就要加很多遍这个方法,造成代码冗余,很不好;
- 假如下次需求让你把 “hello” 改成 “fuck” 的时候,你又得把三个页面都改一遍,这时候你只能抽自己的脸了…
吸取上面的教训,你会有一个新的思路:我们可以直接在store中对name进行一些操作或者加工,从源头解决问题!那么具体应该怎么写呢?这时候,本次将要介绍的这个Gette
r利器闪亮登场!
const store = new Vuex.Store({
state:{
// 存放的键值对就是所要管理的状态
name:'helloVueX',
number: 0,
list: [
{id: 1, name: '1111'},
{id: 2, name: '2222'}
]
},
getters: {
getMessage(state) {
let _obj = JSON.stringify(state.list);
let arr = JSON.parse(_obj);
arr.forEach(val => {
val.name = `hello ${val.name}`;
});
return arr;
}
},
mutations: {},
actions: {},
modules: {}
});
2.1 通过属性去访问
Getter 会暴露为 store.getters
对象,你可以以属性的形式访问这些值:
<h1 v-for="item in $store.getters.getMessage" :key="item.id">{{item.name}}</h1>
或
<template>
<div id="app">
<h1 v-for="item in getMessage" :key="item.id">{{item.name}}</h1>
</div>
</template>
<script>
export default {
name: 'App',
computed: {
getMessage() {
return this.$store.getters.getMessage;
}
}
};
</script>
2.2 通过方法去访问
你也可以通过让getter
返回一个函数,来实现给getter
传参。在你对 store
里的数组进行查询时非常有用。
getters: {
getMessageById: (state) => (id) => {
return state.list.find(val => val.id === id);
}
}
<script>
export default {
name: 'App',
computed: {
getMessageById() {
return this.$store.getters.getMessageById(2);
}
}
};
</script>
!!!注意,getter
在通过方法访问时,每次都会去进行调用,而不会缓存结果。
2.3 mapGetters辅助函数
mapGetters
辅助函数仅仅是将 store
中的getter
映射到局部计算属性,官方建议我们可以使用mapGetters
去解构到计算属性中,就像使用mapState
一样,就可以直接使用this调用了,就像下面这样:
<template>
<div id="app">
<h1 v-for="item in getMessage" :key="item.id">{{item.name}}</h1>
</div>
</template>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'App',
computed: {
...mapGetters(['getMessage'])
}
};
</script>
3、了解如何修改值:Mutation
说到修改值,有的同学就会想到这样写:
// 错误示范
this.$store.state.XXX = XXX;
为什么上面是错误的写法?因为这个store
仓库比较奇怪,你可以随便拿,但是你不能随便改,举个例子:
假如你打开微信朋友圈,看到你的好友发了动态,但是动态里有个错别字,你要怎么办呢?你可以帮他改掉吗?当然不可以!我们只能通知他本人去修改,因为是别人的朋友圈,你是无权操作的,只有他自己才能操作,同理,在vuex
中,我们不能直接修改仓库里的值,必须用vuex
自带的方法去修改,这个时候,Mutation
闪亮登场了!
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个mutation
都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state
作为第一个参数,你可以想store.commit
传入额外的参数,即mutation
的载荷:
mutations: {
incrementN(state, payload) {
state.number += payload;
},
// 对象风格的提交方式
increment(state, payload) {
state.number += payload.amount;
}
}
<script>
export default {
name: 'App',
mounted() {
console.log(`旧值:${this.$store.state.number}`);
this.$store.commit('incrementN', 5);
console.log(`新值N:${this.$store.state.number}`);
// 对象风格的提交方式
this.$store.commit('increment', {amount: 10});
console.log(`新值:${this.$store.state.number}`);
}
};
</script>
3.1 Mutation 需遵守 Vue 的响应规则
既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。
这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:
- 最好提前在你的 store 中初始化好所有所需属性。
- 当需要在对象上添加新属性时,你应该:使用
Vue.set(obj, 'newProp', 123)
3.2 Mutation 必须是同步函数
这里说一条重要原则:Mutations里面的函数必须是同步操作,不能包含异步操作!(别急,后面会讲到异步)。
3.3 在组件中提交 Mutation
就像最开始的mapState
和mapGetters
一样,我们在组件中可以使用mapMutations
以代替this.$store.commit('XXX')
,是不是很方便呢?
你可以在组件中使用this.$store.commit('xxx')
提交mutation
,或者使用 mapMutations
辅助函数将组件中的 methods
映射为 store.commit
调用(需要在根节点注入store
)。
<script>
import {mapState, mapMutations} from 'vuex';
export default {
name: 'App',
computed: {
...mapState(['number'])
},
mounted() {
console.log(`旧值:${this.number}`);
this.incrementN(5);
console.log(`新值N:${this.number}`);
this.increment({amount: 10});
console.log(`新值:${this.number}`);
},
methods: {
...mapMutations(['incrementN', 'increment'])
}
};
</script>
4、了解异步操作:Actions
Actions存在的意义是假设你在修改state的时候有异步操作,vuex作者不希望你将异步操作放在Mutations中,所以就给你设置了一个区域,让你放异步操作,这就是Actions。
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
注册一个简单的action:
const store = new Vuex.Store({
state:{
// 存放的键值对就是所要管理的状态
name:'helloVueX',
number: 0
},
mutations: {
setNumberIsWhat(state, payload) {
state.number = payload.number;
}
},
actions: {
incrementAction(context) {
// 我们模拟一个异步操作,1秒后修改number为888
return new Promise(resolve => {
setTimeout(() => {
context.commit('setNumberIsWhat', {number: 888});
resolve();
}, 1000);
});
}
}
});
组件内 vue.js
mounted() {
console.log(`旧值:${this.number}`);
this.$store.dispatch('incrementAction').then(() => {
console.log(`新值:${this.number}`);
});
},
Action
函数接受一个与 store
实例具有相同方法和属性的context
对象,因此你可以调用context.commit
提交一个 mutation
,或者通过 context.state
和 context.getters
来获取 state
和getters
。
4.1 分发Action
Action
通过 store.dispatch
方法触发:
store.dispatch('incrementAction')
Actions 支持同样的载荷方式和对象方式进行分发:
// 以载荷形式分发
store.dispatch('incrementAction', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAction',
amount: 10
})
4.2 在组件内分发Action
你如果不想一直使用this.$store.dispatch('XXX')
这样的写法调用action
,你可以采用mapActions
的方式,把相关的actions
解构到methods
中,用this
直接调用。
import {mapActions, mapState} from 'vuex';
export default {
name: 'App',
computed: {
...mapState(['number'])
},
mounted() {
console.log(`旧值:${this.number}`);
this.incrementAction().then(() => {
console.log(`新值:${this.number}`);
});
},
methods: {
...mapActions(['incrementAction'])
}
};
5、了解模块化状态管理:Modules
当项目庞大,状态非常多时,可以采用模块化管理模式。Vuex
允许我们将 store
分割成模块(module)。每个模块拥有自己的state
、mutation
、action
、getter
、甚至是嵌套子模块
——从上至下
进行同样方式的分割。
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
5.1 模块的局部状态
- 对于模块内部的
mutation
和getter
,接收的第一个参数是模块的局部状态对象。 - 对于模块内部的
getter
,根节点状态会作为第三个参数暴露出来: - 对于模块内部的
action
,局部状态通过context.state
暴露出来,根节点状态则为context.rootState
const moduleA = {
// namespaced: true,
state: () => ({
name:'helloModuleA',
number: 2
}),
mutations: {
increment(state, payload) {
// state.number = 2
state.number += payload;
}
},
getters: {
getMessage(state, getters, rootState) {
return state.number + rootState.number; // 2 + 0 = 2
}
},
actions: {
getSumAction(context) {
if (context.state.number === 2 && context.rootState.number === 0) {
context.commit('increment');
}
}
}
};
const store = new Vuex.Store({
state:{
// 存放的键值对就是所要管理的状态
name:'helloVueX',
number: 0
},
getters: {
getMessage(state) {
return `hello ${state.name}`;
}
},
mutations: {
increment(state, payload) {
// state.number = 0
state.number += payload;
}
},
modules: {
a: moduleA
}
});
5.2 命名空间
默认情况下,模块内部的 action
、mutation
和getter
是注册在全局命名空间的——这样使得多个模块能够对同一 mutation
或 action
作出响应。
如果希望你的模块具有更高的封装度
和复用性
,你可以通过添加namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter
、action
及mutation
都会自动根据模块注册的路径调整命名。
mounted() {
console.log(`旧值:${this.number}`); // 0
console.log(`a模板旧值:${this.$store.state.a.number}`); // 2
this.$store.commit('increment', 5);
this.$store.commit('a/increment', 40);
console.log(`新值:${this.number}`); // 5
console.log(`a模板新值:${this.$store.state.a.number}`); // 42
}
辅助函数用法:
<script>
import {mapState, mapGetters, mapMutations, mapActions} from 'vuex';
export default {
name: 'App',
computed: {
// ...mapState(['number']),
...mapState({
number: state => state.number,
numberA: state => state.a.number
}),
// ...mapGetters(['getMessage'])
...mapGetters(['getMessage', 'a/getSum'])
},
mounted() {
// console.log(this.number);
// console.log(this.numberA);
// console.log(`a模板旧值:${this.numberA}`);
// this['a/increment'](40);
// console.log(`a模板新值:${this.numberA}`);
// console.log(`${this['a/getSum']}`);
// this.$store.dispatch('a/getSumAction');
// this['a/getSumAction']();
console.log(`a模板新值:${this.numberA}`);
},
methods: {
// ...mapMutations(['increment', 'reduce']),
...mapMutations(['a/increment', 'increment', 'reduce']),
// ...mapActions(['incrementAction'])
...mapActions(['a/getSumAction', 'incrementAction'])
}
};
</script>
5.3 模块动态注册
在 store 创建之后,你可以使用 store.registerModule
方法注册模块:
import Vuex from 'vuex'
const store = new Vuex.Store({ /* 选项 */ })
// 注册模块 `ModuleA`
store.registerModule('ModuleA', {
// ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['Modulea', 'ModuleA'], {
// ...
})
之后就可以通过 store.state.ModuleA
和 store.state.Modulea.ModuleA
访问模块的状态。
你可以通过store.hasModule(ModuleA)
方法检查该模块是否已经被注册到store
总结
看到这里你肯定对vuex
不陌生了,你会安装它,配置它,读取state
的值,甚至修饰读(Getter
),然后你会修改里面的值了(Mutation
),假如你有异步操作并且需要修改state
,那你就要使用Action
,这样,你就可以在你的项目中用起来vuex
啦!加油吧!