深入学习-Vuex 文档解读

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 里面封装

image.png

插件扩展

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 的时光旅行?

  1. 通过vuex的执行的操作会被记录下来
  2. 可以选择操作记录,返回回退到此操作时的状态

 
 
 
原文地址:https://www.cnblogs.com/the-last/p/11391731.html