Vuex 管理状态
state
单一状态树,意思是一个对象包含了全部应用层级状态,Store将作为唯一数据源。
每个应用,仅仅有且只有一个 store 实例!
mapState
当一个组件组件组件需要多个状态值时,可以调用 mapState函数赋值给 computed 返回是对象。
// mapState 基本用法,3种: 箭头函数, 字符串, 函数.
computed:mapState({
//1,箭头函数 countFromStore:state=>state.count
//2,字符串 countFromStore: 'count' 等价于 state=>state.count
//3,函数 需要使用组件内数据访问this时,还可以用函数
countFromStore(state) {
return state.count+this.localCount;
}
}),
// 如果同名取Store中的值,可以取count字符串放在数组中传递给 mapState.
computed:mapState([
'count' // 可以访问多个状态
]),
// mapState返回对象所以可以扩展computed对象.
computed: {
...mapState([
'count'
]),
localCount() {return this.localCount + this.$store.state.count;}
}
getters
可以吧 getters 当作是 state store的计算属性,传参是state .
Vue.use(Vuex);
conststore=newVuex.Store({
state: {
count:1,
name:'NewStation',
todos: [
{ id:1, text:'todos -1', done:true },
{ id:2, text:'todos -2', done:false },
]
},
mutations: { // this.$store.commit('changeName') 触发commit更新store
increment(state) {
state.count++
},
changeName(state) {
state.name='NewPlace'
}
},
getters: { // this.$store.getters.doneTodos 获取getters编译后的值
doneTodos:state=> {
returnstate.todos.filter(todo=>todo.done)[0].done;
},
doneState: (state, getter)=> {
return getter.doneTodos + 'getter 作为其他 getter 的传参';
}
}
});
mapGetters
辅助函数,,支持2种方式转换,把store中的getters 可以支持传参,映射到局部计算属性。
computed: {
...mapGetters([ 数组
'doneTodos', // 可以直接调用
]),
...mapGetters({ 对象
aliasFillname:'getFillName' //给 getter 加别名。
}),
}
mapMutation
更新 store状态 的唯一方法是提交mutation。 Vuex中的 mutation 类似于事件:每个 mutation 都有自己的事件类型和回调函数,这个回调函数就是我们状态更改的地方,并接受 state 作为第一个参数。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
store.commit('increment') 只能用这个方式修改状态!
触发mutation更新状态,触发一个叫 increment 类型的事件,并回调执行 这个类型的注册函数。
提交负载Payload
可以给commit传参,称为 Payload负载,通常是一个对象。
this.$store.commit('increment', { account: 'local' })
对象的风格提交
只给mutation传递对象,表示整个对象被当做 Payload,被解析,mutation中获取的方式是不变的。
this.$store.commit({
type: 'increment',
account: 'local'
})
Mutation需遵守vue响应规则
1,提前在store中初始化所有有声明的属性值
2,获取对象值之后,如需修改推荐调用 Vue.set (obj, 'newkey', 'newvalue') 或者 这样 {...obj, newProp: 123}
store的对象放在前。
3,推荐使用常量const方式命名mutation中事件的类型名,也可以不用。
Mutation必须是同步函数
并不是异步调用mutation不会触发状态变化,而是会造成状态无法追踪,不好调试。
在组件中提交mutation
methods: {
...mapMutations([ 数组
'doneTodos', // 可以直接调用 this.doneTodos('参数') 触发doneTodos事假类型 映射 // 映射 this.$store.commit
]),
...mapMutations({ 对象
aliasFillname:'getFillName' //给 getter 加别名。 使用别名调用
}),
}
Action
Action类似于mutation,不同之处是
1,Action是提交mutation,不直接修改store的值。
2,Action可以包含任何异步的操作。
接受一个和mutation一样的对象属性,可以使用解构
actions: {
incrementPlus ({ commit }) {
commit('increment');
setTimeout(() => {
commit('actionOther'); // 可以触发多个commit
}, 0)
}
}
this.$store.dispatch('incrementPlus') 可以直接使用消息派发
dispatch 的调用修改状态的方式,和 mutation 一致,可以接受Payload参数,可以接受对象更新状态。
好处可以在Action中定义任意多的 异步处理。
methods: {
...mapActions([ 数组
'doneTodos', // 可以直接调用 this.doneTodos('参数') 触发doneTodos事假类型
]),
...mapActions({ 对象
aliasFillname:'getFillName' //给 getter 加别名。 使用别名调用
}),
}
组合Action
1,Action可被其他Action调用
2,Action内部可以是用 Promise 或 时间事件
actionIncrement ({commit}) {
return new Promise((resolve) => {
setTimeout(() => {
commit('increment');
resolve()
});
})
},
actionB ({ dispatch, commit }) {
return dispatch('actionIncrement').then(() => {
commit({
type: 'changeName',
account: '1000'
})
})
}
dispatch actionB 会执行被应用的Action动作 触发联动,包括异步的处理和请求。
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
module
由于我们使用一个单一的状态树,因此会导致会有一个很大的对象需要维护。
那怎么维护呢? Vuex可以使用 module 分割模块,每个模块都有自己独立的 state mutation getter Action 甚至是嵌套子module,自上而下一样的方式分割。
modules
modules: { 丰富在 Vuex.Store 中,和 state 同级
a:moduleA,
b:moduleB,
}
store.state.a // -> moduleA 的状态 分别访问各自module的状态,getter Action 以此类推。
store.state.b // -> moduleB 的状态
局部状态
module 内部的 mutation getters ,内部函数的首个参数为 当前module的state,有隔离。
那 如何获取到 根module的 state 呢?(根的commit 和 getter 好像不能访问。)
根module,可以从第三个参数 rootState 正常获取
超级无敌神奇,在其他module 访问 rootState ,commit rootState ,其他module会直接值变化。
命名空间
以上局部模块,可访问rootState的特性,也使得在组件中访问 state或getter时并不知道,是属于哪个模块的。
这可怎么办呢? 可以用namespace 命名空间,更能复用,更高的封装。
命名空间可以访问 getter 不能直接访问 state!那咋办,有两个方法可以。
1,保留像之前一样的访问形式,可以因为vuex提供的api : createNamespacedHelpers
import { mapGetters, createNamespacedHelpers} from"vuex";
const { mapState } =createNamespacedHelpers('account') ; account就是对应的命名空间名。
2,在computed 中直接使用,需要指明命名空间名
...mapState('account', {userNa: 'userName'}) 添加命名空间 ,还可以加别名。
因此,getter dispatch commit 会局部化,无需修改代码,只用加上 namespaced 。
命名空间内访问全局state
命名空间内的state getter 已经无法直接被访问可以加修饰访问,那还可以和全局state通信吗? 嗯可以。
1,从全局module获取 在 getters 内访问全局 提供了第三,第四 个参数
someGetter (state, getters, rootState, rootGetters) {}
someAction ({ dispatch, commit, getters, rootGetters }) {}
2,如果从命名空间dispatch 信息到全局呢?
dispatch('someOtherAction', {count: 999}, { root: true })
commit('someMutation', null, { root: true })
可以在命名空间内定义全局可访问的Action吗,嗯可以
modules: {
foo: {
namespaced: true,
actions: {
someAction: {
root: true,
handler (namespacedContext, payload) { ... } // -> 'someAction' 全局可dispatch
}
}
}
}
模块动态注册
store 创建之后,store.registerModule
动态注册模块,需要在组件实例化之后,所以你在不是组件实例化之外的地方调用,vuex报找不到addChild,坑爹。
// 注册模块 `myModule` 可以再 created mounted 之后
store.registerModule('myModule', {
// ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
访问,通过 store.state.myModule store.state.nested.myModule (嵌套) 访问模式状态
动态注册模块,使得在vuex中更灵活的配置状态管理,比如 vuex-router-sync 就是通过在状态中注册动态的module扩展应用
卸载模块,unregisterModule 卸载动态添加的模块,不能卸载静态的已声明的模块。
模块添加时不希望将之前的state覆盖,可以保留state
store.registerModule(a, 'module', { preserveState: true })
模块重用
1,创建多个store(ssr尽量不要用)
2,多次注册同一个模块
所以,需要纯函数并且保持返回一致的状态,对象被引用共享,导致状态对象被修改,修改store或者模块之间数据
被污染,解决办法还是通过像 vue data (){} 一样的处理方式
const MyReusableModule = {
state () {
return {
foo: 'bar'
}
},
// mutation, action 和 getter
}
项目结构
vuex并不限制是什么样的代码结构,但是你会发现如果不给 mutation state getter Action 分分类可能会比较难维护。 vuex 给出了一些建议,关于构建状态管理项目的建议。
1,应用层级的状态应该集中在单个状态对象
2,提交 mutation 是修改状态的唯一对象,过程是同步的
3,异步的逻辑应该在Actions 里面封装
插件扩展
Vuex的store接受plugins参数,这个选项暴露出每次mutation的钩子,vuex 插件是一个函数,唯一的参数是store
const myPlugin = store => {
// 当 store 初始化后调用
store.subscribe((mutation, state) => {
// 每次 mutation 之后调用
// mutation 的格式为 { type, payload } type是事件类型,Payload是载荷传参,。
// state 值包含根store(全局store)的state值,module值,和registerModule注册的module值!!
})
}
store的存储和更新,可以和其他事件结合使用,比如可以和websocket的onchat事件、onemit提交事件合并使用。反正就是可以在插件里,想干嘛干嘛各种自由。
State更新前生成快照
为甚需要生成快照呢,因为你需要对比state的前后变化,有时候是需要的考虑到性能(前端必须考虑性能)。
实现state快照进行前后对比,一般是这样的代码: (官网建议快照只在开发阶段使用)
因为Subscribe 监听的是 store 所以store内变化都会反映。
内置logger插件
importcreateLoggerfrom'vuex/dist/logger';
可以再 createLogger函数传入一些配置项,并把结果传给 plugins。
严格模式
这里严格模式是 Store state的严格模式,并非插件或module的严格模式!
strict: true.
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。
表单处理
在使用严格模式时,如果state 使用 v-model 会比较棘手。
(非严格模式下,控制台会有如下提示,console打印 Computed property "count" was assigned to but it has no setter.) 提示警告,computed中的值没有setter属性。
(严格模式下,和非严格模式下,这种-v-model的方式都会有一样的报错。)
但是,还是要更新count的值,那咋办呢,可以监听 事件
<input :value="message" @input="updateMessage">
:value,message,bind绑定message,单向
message,是state中的值,mapState映射
updateMessage是input输入方法,触发这个方法,在这个方法内执行 commit 更新state,
然后,形成一个更新的环。
这样一来,用不了v-model指令,而且更新起来比较繁琐,得不偿失。
继续往下看。
双向绑定
<inputv-model="message">
computed: {
message: {
get () {
return this.count
},
set (value) {
this.updateMessage({count: value})
}
}
}
利用 computed 中属性值 getter setter 方法合并出一个和Store双向绑定的值。
单元测试
测试mutation,这个很好测试,都是传参的函数。 举个栗子
使用 mocha chai 进行测试,该装的插件都装上。
package.json -> scripts
"test": "mocha --require babel-register ./src/store/index.spec.js"
测试Action,
有点小麻烦,需要一些测试基本知识
相当陌生,暂时搁置。。。。
热重载
webpack 提供了内置的插件 module.hot 结合 Vuex.hotUpdate .
Hot Module Replacement
Vuex 基于这个热重载插件,热重载mutation,module,action,getter,对于mutation 和模块,可以使用
store.hotUpdate()
if (module.hot) {
module.hot.accept([
'./getters',
'./actions',
'./mutations'
], () => {
store.hotUpdate({
getters:require('./getters'),
actions:require('./actions'),
mutations:require('./mutations')
})
}
Store 对生命周期的影响
初始阶段
就是组件实例化和mounted的过程,和从data或props中取值并没有区别。
App ---beforeCreate--
App ---created---
App ---beforeMount---
Helloworld ---beforeCreate--
Helloworld ---created---
Helloworld ---beforeMount---
Helloworld ---mounted---
App ---mounted---
store的state更新阶段
App ---beforeUpdate---
Helloworld ---beforeUpdate---
Helloworld ---updated---
App ---updated---
时光旅行 - Vuex状态值
什么是Store 的时光旅行?
- 通过vuex的执行的操作会被记录下来
- 可以选择操作记录,返回回退到此操作时的状态