目录
- 1 引入 vuex
- 2 state 访问状态对象
- 3 mutations(同步) 模板获取方法
- 4 getters 计算过滤操作
- 5 actions 异步修改状态
- 6 modules 模块
1 引入 vuex
前提是已经用 Vue 脚手架工具构建好项目:
vue init webpack vue-test
1:安装
cnpm i vuex --save-dev
2:编写 store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
count: 10
}
const mutations = {
increment(state) {
state.count++
},
reduce(state) {
state.count--
}
}
export default new Vuex.Store({
state,
mutations
})
3:主入口 main.js,引入 store.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store/store'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
4:使用
<template>
<div class="hello">
<h1>home</h1>
<div>
{{$store.state.count}} <!-- 页面获取对应的 state -->
<button @click="increment">新增</button>
<button @click="reduce">减少</button>
</div>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
msg: 'Welcome to Your Vue.js App'
};
},
methods: {
increment: function() {
this.$store.commit('increment'); // commit 触发调用 store 中的方法;
},
reduce: function() {
this.$store.commit('reduce');
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
2 访问状态对象 state
store 中的 state,即为访问状态对象,它就是我们 SPA(单页应用程序)中的共享值。
状态对象赋值给内部对象,也就是把 stroe.js 中的值,赋值给我们模板里 data 中的值。有三种赋值方式
一、 通过 computed 的计算属性直接赋值
computed 属性可以在输出前,对 data 中的值进行改变,我们就利用这种特性把 store.js 中的 state 值赋值给我们模板中的 data 值。
<template>
<div class="hello">
<h1>home</h1>
<div>
{{$store.state.count}} -- {{count}}
<button @click="increment">新增</button>
<button @click="reduce">减少</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
// count: 0
};
},
computed: {
count() {
return this.$store.state.count;
}
},
methods: {
increment: function() {
this.$store.commit('increment');
},
reduce: function() {
this.$store.commit('reduce');
}
}
};
</script>
这里需要注意的是 return this.$store.state.count
这一句,一定要写 this,要不你会找不到 $store 的。这种写法很好理解,但是写起来是比较麻烦的,那我们来看看第二种写法。
二、通过 mapState 的对象来赋值
我们首先要用 import 引入 mapState。
import { mapState } from 'vuex';
<template>
<div class="hello">
<h1>home</h1>
<div>
{{$store.state.count}} -- {{count}}
<button @click="increment">新增</button>
<button @click="reduce">减少</button>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
data() {
return {
// count: 0
};
},
computed: mapState({ // !!!
count: state => state.count, // 理解为传入 state 对象,修改 state.count 属性
}),
methods: {
increment: function() {
this.$store.commit('increment');
},
reduce: function() {
this.$store.commit('reduce');
}
}
};
</script>
三、通过 mapState 的数组来赋值
<template>
<div class="hello">
<h1>home</h1>
<div>
{{$store.state.count}} -- {{count}}
<button @click="increment">新增</button>
<button @click="reduce">减少</button>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
data() {
return {
// count: 0
};
},
computed: mapState(['count']), // !!!
methods: {
increment: function() {
this.$store.commit('increment');
},
reduce: function() {
this.$store.commit('reduce');
}
}
};
</script>
这个算是最简单的写法了,在实际项目开发当中也经常这样使用。
3 模板获取 Mutations(同步) 方法
方式一:$store.commit 触发
<template>
<div class="hello">
<h1>home</h1>
<div>
{{$store.state.count}} -- {{count}}
<button @click="increment({n: 111})">新增</button>
<button @click="reduce">减少</button>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
export default {
name: 'HelloWorld',
data() {
return {
// count: 0
};
},
computed: mapState(['count']),
methods: {
increment: function() {
this.$store.commit('increment', { n: 100 }); // 参数
},
reduce: function() {
this.$store.commit('reduce');
}
}
};
</script>
方式二:mapMutations 数组
<template>
<div class="hello">
<h1>home</h1>
<div>
{{$store.state.count}} -- {{count}}
<button @click="increment({n: 111})">新增</button>
<button @click="reduce">减少</button>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
export default {
data() {
return {
// count: 0
};
},
computed: mapState(['count']),
methods: mapMutations(['increment', 'reduce'])
};
</script>
方式三:mapMutations 解构
<template>
<div class="hello">
<h1>home</h1>
<div>
{{$store.state.count}} -- {{count}}
<button @click="add({n: 12})">新增add</button>
<button @click="increment({n: 111})">新增</button>
<button @click="reduce">减少</button>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
export default {
data() {
return {
// count: 0
};
},
computed: mapState(['count']),
methods: {
...mapMutations(['increment', 'reduce']),
...mapMutations({ add: 'increment' }) // 映射 this.add() 为 this.$store.commit('increment')
}
};
</script>
store.js:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
count: 10
}
const mutations = {
increment(state, obj) {
state.count += obj.n
},
reduce(state) {
state.count--
}
}
export default new Vuex.Store({
state,
mutations
})
4 getters 计算过滤操作
getters 从表面是获得的意思,可以把他看作在获取数据之前进行的一种再编辑,相当于对数据的一个过滤和加工。你可以把它看作 store.js 的计算属性。
比如我们现在要对 store.js 文件中的 count 进行一个计算属性的操作,就是在它输出前,给它加上 100。我们首先要在 store.js 里用 const 声明我们的 getters 属性。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
count: 10
}
const mutations = {
increment(state, obj) {
state.count += obj.n
},
reduce(state) {
state.count--
}
}
const getters = {
handleCount: function (state) {
return (state.count += 100);
}
}
export default new Vuex.Store({
state,
mutations,
getters
})
在 store.js 里的配置算是完成了,我们需要到模板页对 computed 进行配置。
<template>
<div class="hello">
<h1>home</h1>
<div>
{{$store.state.count}} -- {{count}}
<button @click="add({n: 12})">新增add</button>
<button @click="increment({n: 111})">新增</button>
<button @click="reduce">减少</button>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
export default {
name: 'HelloWorld',
data() {
return {
// count: 0
};
},
computed: {
...mapState(['count']),
handleCount() {
return this.$store.getters.count;
},
},
methods: {
...mapMutations(['increment', 'reduce']),
...mapMutations({ add: 'increment' }) // 映射 this.add() 为 this.$store.commit('increment')
}
};
</script>
另一个方式使用 mapGetters:
<template>
<div class="hello">
<h1>home</h1>
<div>
{{$store.state.count}} -- {{count}}
<button @click="add({n: 12})">新增add</button>
<button @click="increment({n: 111})">新增</button>
<button @click="reduce">减少</button>
</div>
</div>
</template>
<script>
import { mapState, mapMutations, mapGetters } from 'vuex';
export default {
name: 'HelloWorld',
data() {
return {
// count: 0
};
},
computed: {
...mapState(['count']),
...mapGetters(['handleCount'])
},
methods: {
...mapMutations(['increment', 'reduce']),
...mapMutations({ add: 'increment' }) // 映射 this.add() 为 this.$store.commit('increment')
}
};
</script>
!!!需要注意的是,你写了这个配置后,在每次 count 的值发生变化的时候,都会进行加 1100 的操作。
5 actions 异步修改状态
actions 和之前讲的 Mutations 功能基本一样,不同点是,actions 是异步的改变 state 状态,而 Mutations 是同步改变状态。
store 中声明 actions
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
count: 10
}
const mutations = {
increment(state, obj) {
state.count += obj.n
},
reduce(state) {
state.count--
}
}
const getters = {
handleCount: function (state) {
return (state.count += 100);
}
}
const actions = {
incrementAsync(context) {
context.commit('increment', { n: 11 });
},
reduceAsync(context) {
context.commit('reduce');
}
}
export default new Vuex.Store({
state,
mutations,
getters,
actions
})
模板中使用 actions:
<template>
<div class="hello">
<h1>home</h1>
<div>
{{$store.state.count}} -- {{count}}
<button @click="add({n: 12})">新增add</button>
<button @click="increment({n: 111})">新增</button>
<button @click="reduce">减少</button>
<button @click="incrementAsync({n: 2})">新增</button>
<button @click="reduceAsync">减少</button>
</div>
</div>
</template>
<script>
import { mapState, mapMutations, mapGetters, mapActions } from 'vuex';
export default {
data() {
return {
msg: 'Welcome to Your Vue.js App'
// count: 0
};
},
computed: {
...mapState(['count'])
},
methods: {
...mapMutations(['increment', 'reduce']),
...mapMutations({ add: 'increment' }), // 映射 this.add() 为 this.$store.commit('increment')
...mapActions({
incrementAsync: 'incrementAsync',
reduceAsync: 'reduceAsync'
})
}
};
</script>
6 modules
命名空间 namespaced
这里引入 namespaced,默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。
moduleA.js
const state = {
count: 10,
count2: 1
}
const mutations = {
increment(state, obj) {
state.count += obj.n
},
reduce(state) {
state.count--
}
}
const getters = {
handleCount: function (state) {
return (state.count += 100);
},
handleCount2: function (state) {
return (state.count2 += 10);
}
}
const actions = {
incrementAsync(context) {
context.commit('increment', { n: 11 });
},
reduceAsync(context) {
context.commit('reduce');
}
}
export default {
namespaced: true,
state,
mutations,
getters,
actions
}
store.js,配置 modules,引入 moduleA。
import Vue from 'vue'
import Vuex from 'vuex'
import moduleA from './moduleA'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
aa: moduleA
}
})
组件引用,如果模块有 namespaced 为 true,则需要带上命名空间,如果模块没有 namespaced,则模块默认为全局;
注意跟全局 store 区别。
<template>
<div class="hello">
<h1>home</h1>
<div>
{{$store.state.aa.count}} --- {{count}} --- {{count2}}
<button @click="add({n: 12})">新增add</button>
<button @click="increment({n: 111})">新增increment</button>
<button @click="reduce">减少reduce</button>
<button @click="incrementAsync({n: 2})">新增incrementAsync</button>
<button @click="reduceAsync">减少reduceAsync</button>
</div>
</div>
</template>
<script>
import { mapState, mapMutations, mapGetters, mapActions } from 'vuex';
export default {
data() {
return {
msg: 'Welcome to Your Vue.js App'
};
},
computed: {
...mapState({
count: state => state.aa.count
}),
count2() {
return this.$store.state.aa.count2;
}
},
methods: {
// ...mapMutations(['aa/increment', 'aa/reduce']), // !!!不能这样写,报错,应该使用下面对象的写法
...mapMutations({
increment: 'aa/increment',
reduce: 'aa/reduce'
}),
...mapMutations({ add: 'aa/increment' }), // 映射 this.add() 为 this.$store.commit('increment')
...mapActions({
incrementAsync: 'aa/incrementAsync',
reduceAsync: 'aa/reduceAsync'
})
}
};
</script>
7 补充
7.1 export 方法创建路由
路由使用函数方式在入口文件引入:
import createRouter from './config/router'
const router = createRouter();
new Vue({
router,
...
})
7.2 export 方法创建 Store
创建 store.js,通过函数创建 Store
import Vuex from 'vuex'
export default () => {
return new Vuex.Store({
state: {
...
},
mutations: {
...
}
})
}
入口引入 Vuex
import createStore from './store/store'
const store = createStore();
new Vue({
store,
...
})
说明:为什么路由和 store 都是 export 一个方法
因为使用服务端渲染,每一次服务端渲染的时候,都应该生成一个新的store,不能使用同一 store,这样会有内存溢出问题 。
7.3 在组件中使用不同的方式获取 store 中的 state:
computed: {
count() {
return this.$store.state.count
}
}
computed: {
...mapState({
counter: 'count'
})
}
computed: {
...mapState({
counter: (state) => state.count
})
}
7.4 通过配置 strict,规范 vuex 修改数据
由于在组件中也是能直接修改 state 数据,不用通过 mutation,为了修改数据的规范,尽量规范通过 mutation 来修改 store,可以在 new Vuex.Stroe 的时候,进行配置:开发使用,正式生产环境应该关掉
import Vuex from 'vuex'
const isDev = process.env.NODE_ENV === 'development'; // 判断是否是开发环境
export default () => {
return new Vuex.Store({
strict: isDev
state: {
...
},
mutations: {
...
}
})
}
7.5 actions 方法参数说明
对于 actions,传入方法的第一个参数是 store,不是 state,传参多个的时候,第二个参数是对象(可以理解为 payload 对象),只有两个参数,actions 的方法是通过 dispatch 来触发的;
7.6 modules 模块扩展说明
1:对于模块,配置 namespaced, 如果没有配置 namespaced,则 mutations、actions、getters 等会被默认放到全局 store;
2:getters 中的方法,通过配置 rootState,可以获取默认全局 state,或者其他模块的 state;
3:默认情况下,对于 mutations 方法获取的 state,为该模块下的 state;
4:actions 方法参数,context 可以拿到 state、commit、rootState 等参数,而 commit 默认触发的是该模块下的 mutations 方法;
actions: {
addText({ state, commit, rootState }) { // 该模块的 context 可以拿到 state、commit、rootState 等参数
commit('changeText', { text: rootState.count }) // commit 默认就是该模块的 mutations
}
}
如果想触发全局 mutations, 可以配置 root,则 commit 会去找全局模块的 mutations
actions: {
addText({ state, commit, rootState }) { // 该模块的 context 可以拿到 state、commit、rootState 等参数
context.commit('changeText', {text: 33}, { root: true }) // 配置 root,则 commit 会去找全局模块的 mutations
}
}
如果想触发其他模块 mutations, 可以配置 root,再加上在 commit 中对应模块的前缀
actions: {
addText({ state, commit, rootState }) { // 该模块的 context 可以拿到 state、commit、rootState 等参数
commit('b/bChangeText', { text: 3 }, { root: true }) // 调用其他模块的 mutations,配置 root,注意 b 模块需要配置 namespaced
}
}
import Vue from 'vue'
import Vuex from 'vuex'
// import moduleA from './moduleA'
import defalutState from './state/state'
import mutations from './mutations/mutations'
import getters from './getters/getters'
import actions from './actions/actions'
Vue.use(Vuex)
const store = new Vuex.Store({ // 热更新 的功能
state: defalutState,
mutations,
getters,
actions,
modules: {
a: {
namespaced: true, // 如果没有加 namespaced,则 mutations、actions、getters 等会被默认放到全局 store
state: {
text: 0
},
mutations: {
changeText(state, { text }) { // mutations 方法获取的 state 为该模块下的 state
state.text = text;
}
},
getters: {
getText(state, getters, rootState) { // 获取全局 state
return state.text + rootState.count
// return state.text + rootState.b.text
}
},
// getters: {
// getText(state) {
// return state.text + 5
// }
// }
actions: {
addText({ state, commit, rootState }) { // 该模块的 context 可以拿到 state、commit、rootState 等参数
commit('changeText', { text: rootState.count }) // commit 默认就是该模块的 mutations
// context.commit('changeText', {text: 33}, { root: true }) // 配置 root,则 commit 会去找全局模块的 mutations
// context.commit('increment', { n: 3 }, { root: true }) // 配置 root,则 commit 会去找全局模块的 mutations
commit('b/bChangeText', { text: 3 }, { root: true }) // 调用其他模块的 mutations,配置 root,注意 b 模块需要配置 namespaced
}
}
},
b: {
namespaced: true,
state: {
text: 22
},
mutations: {
bChangeText(state, { text }) { // mutations 方法获取的 state 为该模块下的 state
state.text = text;
}
}
}
}
})
export default store;
7.7 动态新增模块 registerModule
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store/store'
// 动态新增模块
store.registerModule('c', {
state: {
text: '232w3'
}
})
// store.unregisterModule(c)
// store.unregisterModule(moduleName) 解绑
// https://vuex.vuejs.org/zh/guide/modules.html#%E6%A8%A1%E5%9D%97%E5%8A%A8%E6%80%81%E6%B3%A8%E5%86%8C
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App />'
})
7.8 使用 vuex 后,配置热重载
官网:https://vuex.vuejs.org/zh/guide/hot-reload.html
import Vue from 'vue'
import Vuex from 'vuex'
import defalutState from './state/state'
import mutations from './mutations/mutations'
import getters from './getters/getters'
import actions from './actions/actions'
Vue.use(Vuex)
export default () => {
const store = new Vuex.Store({ // 热更新 的功能
state: defalutState,
mutations,
getters,
actions
})
if (module.hot) {
module.hot.accept([
'./state/state',
'./mutations/mutations',
'./getters/getters',
'./actions/actions'
], () => {
// 1.获取更新后的模块
// 因为 babel6 的模块编译格式问题,这里需要加上.default
const newState = require('./state/state').default;
const newMutations = require('./mutations/mutations').default;
const newGetters = require('./getters/getters').default;
const newActions = require('./actions/actions').default;
// 2.加载新模块
store.hotUpdate({
state: newState,
mutations: newMutations,
getters: newGetters,
actions: newActions
})
})
}
return store;
}
7.9 store 方法 watch
store.watch((state) => state.count + 500, (newCount) => {
console.log('newCount', newCount);
})
第一个参数为函数,需要返回对哪个 store 中哪个 state
进行监听,第二个参数也为方法,参数为新的 state,为回调方法。
7.10 store 方法 subscribe
每次触发 mutation 就会触发 subscribe
store.subscribe((mutation, state) => {
console.log('mutation', mutation)
console.log('state', state)
console.log(mutation.type)
console.log(mutation.payload)
})
7.11 store 方法 subscribeAction
每次触发 action 就会触发 subscribeAction
store.subscribeAction((action, state) => {
console.log('action', action)
console.log('state', state)
console.log(action.type)
console.log(action.payload)
})
7.12 vuex 自定义插件
import Vue from 'vue'
import Vuex from 'vuex'
import defalutState from './state/state'
import mutations from './mutations/mutations'
import getters from './getters/getters'
import actions from './actions/actions'
Vue.use(Vuex)
export default () => {
const store = new Vuex.Store({ // 热更新 的功能
state: defalutState,
mutations,
getters,
actions,
plugins: [ // vuex 定义插件
(store) => {
console.log(store);
}
]
})
if (module.hot) {
module.hot.accept([
'./state/state',
'./mutations/mutations',
'./getters/getters',
'./actions/actions'
], () => {
// 1.获取更新后的模块
// 因为 babel6 的模块编译格式问题,这里需要加上.default
const newState = require('./state/state').default;
const newMutations = require('./mutations/mutations').default;
const newGetters = require('./getters/getters').default;
const newActions = require('./actions/actions').default;
// 2.加载新模块
store.hotUpdate({
state: newState,
mutations: newMutations,
getters: newGetters,
actions: newActions
})
})
}
return store;
}
7.13 加入 vuex 后,项目的架构
说明:
1:整个 vue 应用其实就是一个节点树,从 root 节点开始,一层一层往下渲染我们的节点;
2:store 是独立的,通过注入到整个组件树,所以,其他各个组件可以通过 $store,获取 store 对象,而组价可以通过 commit 或者 dispatch 触发 store,而真正更改 store 中 state 的数据,是在 store 里面。