Vuex的基础学习
小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
1.1 Vuex是什么?
Vuex是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间数据的共享
1.2 使用Vuex统一管理状态的好处
- 能够在Vuex中集中管理共享的数据,易于开发和后期维护
- 能够高效的实现组件之间的数据共享,提高开发效率
- 存储在Vuex中的数据都是响应式的,能够实时的保持数据与页面的同步
什么样的数据才适合存储到vuex中?一般情况下,只有组件之间共享的数据,才有必要存储到vuex中;对于组件的私有数据,依旧存储在组件自身的data中即可
2.1 Vuex的基本使用
-
安装 vuex 依赖包
npm install vuex --save -
导入 vuex 包
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
- 创建
Store对象并导出出去
export default new Vuex.Store({
state: {
//...
},
mutations: {
//...
},
actions: {
//...
},
getters: {
//...
},
modules: {
//...
}
})
4.在 main.js 中导入 store 并挂载到 app 上
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App)
}).$mount('#app')
3.1 Vuex的核心概念
state:state 提供唯一的公共数据源,所有用于共享的数据均放在store的 state 中进行存储mutation: mutation 用于变更 state 中的数据(mutation能且只能用于处理同步操作)action:action 用于处理异步任务getters:getters 用于对 state 中的数据进行加工处理,形成新的数据。modules:项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
3.2 State
export default new Vuex.Store({
state: {
count: 10
}
})
组件访问 state 的方式
- 第一种方式
通过
this.$store.state.<全局状态名>
<template>
<div>
<h3>当前最新的count值为:{{this.$store.state.count}}</h3>
</div>
</template>
- 第二种方式
在vuex中按需导入
mapState函数,通过mapState函数,将当前组件需要的全局数据,映射为当前组件的computed计算属性
<template>
<div>
<h3>当前最新的count值为:{{ count }}</h3>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data() {
return {
//...
}
},
computed: {
...mapState(['count']) // 采用vuex中的原名 count 当作计算属性使用
...mapState({
add:'count' // 将vuex中的 count 映射成当前组件的的 add的计算属性,其实就是起一个别名
})
...mapState({
count:state=>state.count // 箭头函数的方法 映射
})
}
}
</script>
以上三种方法达到的效果是一样的,选择其一即可,后续提到的
mapMutations,mapActions和mapGetters用法类似,就不再一一赘述了
3.3 Mutation
不直接对 state 中的数据进行操作,而采用 mutations 变更 state 中的数据的原因,因为state是实时更新的,mutations无法进行异步操作,而如果直接修改 state 的话是能够异步操作的,当你异步对 state 进行操作时,还没执行完,这时候如果 state 已经在其他地方被修改了,这样就会导致程序存在问题了。所以 state 要同步操作,通过 mutations 的方式限制了不允许异步
export default new Vuex.Store({
state: {
count: 100
},
mutations: {
addN(state, step) {
return state.count += step
},
delN(state, step) {
return state.count -= step
}
}
})
组件访问 mutation 的方式
- 第一种方式
通过
this.$store.commit('<方法名>',【参数】)的方式导入
<template>
<div>
<h3>当前最新的count值为:{{this.$store.state.count}}</h3>
<input type="text" v-model="value">
<button @click = "handlerToAdd" >+N</button>
</div>
</template>
//...
export default {
data() {
return{
value: 0
}
},
methods:{
handlerToAdd(step){
// commit 的作用就是调用某个mutation函数
step = Number(this.value)
this.$store.commit('addN', step)
}
}
}
- 第二种方式
在vuex中按需导入
mapMutations函数,通过mapMutations函数,将当前组件需要的全局数据,映射为当前组件的methods方法
<template>
<div>
<h3>当前最新的count值为:{{ count }}</h3>
<input type="text" name="" id="" v-model="value" />
<button @click="del">-N</button>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
data() {
return {
value: 0
}
},
methods: {
...mapMutations(['delN']),
handlerToDel(step) {
step = this.value
// this.$store.commit('delN', step)
this.delN(step)
}
},
computed: {
...mapState(['count']),
}
}
</script>
3.4 Action
通过异步操作变更数据需要调用
action,但在action中还需要通过调用mutation才能真正变更state中的数据
export default new Vuex.Store({
state: {
count: 100
},
mutations: {
addN(state, step) {
return state.count += step
},
delN(state, step) {
return state.count -= step
}
},
actions: {
addNAsync({ commit }, step) {
setTimeout(() => {
commit('addN', step)
}, 1000)
},
delNAsync({ commit }, step) {
setTimeout(() => {
commit('delN', step)
}, 1000)
}
},
modules: {
}
})
组件访问 mutation 的方式
- 第一种方式
通过
this.$store.dispatch('<函数名>',【参数】)
<template>
<div>
<h3>当前最新的count值为:{{this.$store.state.count}}</h3>
<input type="text" v-model="value">
<button @click= "handlerToAddAsync">+N_Async</button>
</div>
</template>
<script>
export default {
data() {
return {
value: 0,
};
},
methods: {
handlerToAddAsync(step) {
step = Number(this.value);
this.$store.dispatch("addNAsync", step);
}
}
}
</script>
- 第二种方式
在vuex中按需导入
mapActions函数,通过mapActions函数,将当前组件需要的全局数据,映射为当前组件的methods方法
<template>
<div>
<h3>当前最新的count值为:{{ count }}</h3>
<input type="text" v-model="value" />
<button @click= "handlerToDelAsync">-N_Async</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
data() {
return {
value: 0
}
},
methods: {
...mapActions([ 'delNAsync']),
handlerToDelAsync(step) {
step = Number(this.value)
this.delNAsync(step)
}
},
computed: {
...mapState(["count"]),
}
}
</script>
3.5 Getter
getter会对state中的数据进行加工处理,形成新的数据;- 当
state中的数据发生变化,getter的数据也会跟着变化
getter不会修改state中的数据,只会对自己包装的数据进行变更
export default new Vuex.Store({
state: {
count: 0
},
// 只有mutations中定义的函数,才有权利修改 state 中的数据
mutations: {
add(state) {
// 不要在mutations函数中执行异步操作
/* setTimeout(() => {
state.count++
}, 1000) */
state.count++
},
addN(state, step) {
state.count += step
},
delete(state, step) {
if (step) {
state.count-=step
}
state.count-=1
}
},
actions: {
addAsync(context) {
// 在 actions 中,不能直接修改 state 中的数据;
// 必须通过 context.commit() 触发某个mutation 才行
setTimeout(() => {
context.commit('add')
}, 1000)
},
addNAsync(context, step) {
setTimeout(() => {
context.commit('addN', step)
}, 1000)
},
subAsync(context, step) {
setTimeout(() => {
context.commit('delete', step)
}, 1000)
}
},
getters: {
showNum(state) {
return `当前最新的数量是:【${state.count}】`
}
},
modules: {
}
})
- 第一种方式
通过
this.$store.getters.<名称>
<template>
<div>
<h3>当前最新的count值为:{{ this.$store.state.count }}</h3>
<input type="text" v-model="value" />
<button @click="handlerToAdd">+N</button>
<button @click="handlerToAddAsync">+N_Async</button>
<h4>{{$store.getters.showNum}}</h4>
</div>
</template>
- 第二种方式
在vuex中按需导入
mapGetters函数,通过mapGetters函数,将当前组件需要的全局数据,映射为当前组件的computed计算属性
<template>
<div>
<h3>当前最新的count值为:{{ count }}</h3>
<input type="text" v-model="value" />
<button @click="handlerToDel">-N</button>
<button @click="handlerToDelAsync">-N_Async</button>
<h4>{{ showNum }}</h4>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions, mapGetters } from "vuex";
export default {
data() {
return {
value: 0,
};
},
methods: {
...mapMutations(["delN"]),
...mapActions(["delNAsync"]),
handlerToDel(step) {
step = Number(this.value);
this.delN(step);
},
handlerToDelAsync(step) {
step = Number(this.value);
this.delNAsync(step);
},
},
computed: {
...mapState(["count"]),
...mapGetters(["showNum"])
},
};
</script>
3.6 Module
module:可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
下面关于 Module 的内容是我复制的,我觉得写的还是容易理解的,基本上都看的懂,所以我自己就不重新造轮子了 (~~懒果然是程序员的共性啊)
首先是使用Modules时的一般结构:
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
})
新建一个项目体验一下,通过vue –cli新建一个项目vuemodule, 不要忘记安装vuex.
- 在src 目录下新一个login文件夹,在里面新建index.js 用于存放login 模块的状态。 为了简单起见,我把模块下的state, actions,mutations, getters 全放在index.js文件中。先简单给它增加一个状态,userName: “sam”
const state = {
useName: "sam"
};
const mutations = {
};
const actions = {
};
const getters = {
};
// 不要忘记把state, mutations等暴露出去。
export default {
state,
mutations,
actions,
getters
}
- 在src 目录下,再新建一个 store.js 这是根store, 它通过modules 属性引入 login模块。
import Vue from "vue";
import Vuex from "Vuex";
Vue.use(Vuex);
// 引入login 模块
import login from "./login"
export default new Vuex.Store({
// 通过modules属性引入login 模块。
modules: {
login: login
}
})
- 在main.js中引入store, 并注入到vue 根实例中。
import Vue from 'vue'
import App from './App.vue'
// 引入store
import store from "./store"
new Vue({
el: '#app',
store, // 注入到根实例中。
render: h => h(App)
})
- 在 app.vue 中通过computed属性获取到login下的state. 这里要注意,在没有modules 的情况下,组件中通过 this.store.state.模块名.属性名,在这里是 this.$store.state.login.userName
<template>
<div id="app">
<img src="./assets/logo.png">
<h1>{{useName}}</h1>
</div>
</template>
<script>
export default {
// computed属性,从store 中获取状态state,不要忘记login命名空间。
computed: {
useName: function() {
return this.$store.state.login.useName
}
}
}
</script>
当名字改变时和上述 Mutation 以及 Action 操作一致,就不赘述了
在模块中,state 是被限制到模块的命名空间下,需要命名空间才能访问。 但actions 和mutations, 其实还有 getters 却没有被限制,在默认情况下,它们是注册到全局命名空间下的,所谓的注册到全局命名空间下,其实就是我们访问它们的方式和原来没有module 的时候是一样的。
- 其实actions, mutations, getters, 也可以限定在当前模块的命名空间中。只要给我们的模块加一个namespaced 属性:
const state = {
useName: "sam"
};
const mutations = {
CHANGE_NAME (state, anotherName) {
state.useName = anotherName;
}
};
const actions = {
changeName ({commit, rootState},anotherName) {
if(rootState.job =="web") {
commit("CHANGE_NAME", anotherName)
}
},
alertName({state}) {
alert(state.useName)
}
};
const getters = {
localJobTitle (state,getters,rootState,rootGetters) {
return rootGetters.jobTitle + " aka " + rootState.job
}
};
// namespaced 属性,限定命名空间
export default {
namespaced:true,
state,
mutations,
actions,
getters
}
当所有的 actions, mutations, getters 都被限定到模块的命名空间下,我们dispatch actions, commit mutations 都需要用到命名空间。如 dispacth("changeName"), 就要变成 dispatch("login/changeName"); getters.localJobTitle 就要变成 getters["login/localJobTitle"]
app.vue 如下:
<template>
<div id="app">
<img src="./assets/logo.png">
<h1 @click ="alertName">{{useName}}</h1>
<!-- 增加h2 展示 localJobTitle -->
<h2>{{localJobTitle}}</h2>
<!-- 添加按钮 -->
<div>
<button @click="changeName"> change to json</button>
</div>
</div>
</template>
<script>
import {mapActions, mapState,mapGetters} from "vuex";
export default {
// computed属性,从store 中获取状态state,不要忘记login命名空间。
computed: {
...mapState("login",{
useName: state => state.useName
}),
localJobTitle() {
return this.$store.getters["login/localJobTitle"]
}
},
methods: {
changeName() {
this.$store.dispatch("login/changeName", "Jason")
},
alertName() {
this.$store.dispatch("login/alertName")
}
}
}
</script>
有了命名空间之后,mapState, mapGetters, mapActions 函数也都有了一个参数,用于限定命名空间,每二个参数对象或数组中的属性,都映射到了当前命名空间中。
<script>
import {mapActions, mapState,mapGetters} from "vuex";
export default {
computed: {
// 对象中的state 和数组中的localJobTitle 都是和login中的参数一一对应。
...mapState("login",{
useName: state => state.useName
}),
...mapGetters("login", ["localJobTitle"])
},
methods: {
changeName() {
this.$store.dispatch("login/changeName", "Jason")
},
...mapActions('login', ['alertName'])
}
}
</script>