美文网首页程序员Web前端之路
VUE组件系统极速入门2--vuex

VUE组件系统极速入门2--vuex

作者: flow__啊 | 来源:发表于2018-01-30 21:46 被阅读55次

    看完了官网vuex一头雾水的,比如vuex中的...mapState、countAlias: 'count'等。没看过官网也没事,希望对你们有帮助。
    下文都是我的个人理解,官网的就引用了代码,也许存在着很多错误的描述或者理解,请各位请教,我会改正的。

    这个坑之前就深挖了~现在是时候填了。
    为什么要有vuex?
    它是管理组件与组件通信的库。
    子组件与父组件之间通信还是很方便的。但是如果是同级组件,或者是其它的复杂情况,比如:

    • a.vue中的子组件想与b.vue中的子组件的通迅
    • a.vue想与b.vue中的子组件的通迅
    • a.vue中的子组件想与b.vue通迅
    • a.vue的子组件的子组件想与b.vue的子组件通迅
    • .....

    就是为了解决了类似的问题~
    我们把事件想简单点吧,如果你管理那么多组件(这个改变了那个的数据,那个改变了这个的数据,那个组件又多了点什么,这个又少了点什么),你会怎么做呢?就说想法,不用实现。
    我的想法是:没有蛀牙 把整个应用的数据全部集中起来,谁想用作显示都无条件同意,而谁想改,改完了一定要通知所以用到这个数据显示的组件,还不是美滋滋?
    vuex也是这个意思,它一共分为以下5个部分

    • state 放数据的
    • Getter 获取数据的
    • Mutation 同步改变数据的
    • Action 异步改变数据(利用Mutation实现)
    • Module 可以把全局的数据仓库分为几个单独的模块,比如用户一个,文章一个。

    1 state

    1.1 state 定义

    const store = new Vuex.Store({
      state: {
        count: 0
      },
      mutations: {
        increment (state) {
          state.count++
        }
      }
    })
    

    然后mutations先提一下怎么用吧(官网的例子,原汁原味 )

    store.commit('increment')
    
    console.log(store.state.count) // -> 1
    

    然后要把这货放在组件里,如果放入父组件中,那么子组件也可以使用。通过 this.$store。

    //普通的定义
    const app = new Vue({
      el: '#app',
      // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
      store,
      components: {XXX},
      template: `XXX`
    })
    //*.vue
    export default {
      el: '#app',
      store,
      components: {XXX}
    }
    </script>
    
    

    1.2 关于mapState

    mapState的作用说到底,就是两个作用,先说第一个:别名。
    上官网的例子,真的是要了亲命了唉):

    // 在单独构建的版本中辅助函数为 Vuex.mapState
    import { mapState } from 'vuex'
    
    export default {
      // ...
      computed: mapState({
        // 箭头函数可使代码更简练
        count: state => state.count,
    
        // 传字符串参数 'count' 等同于 `state => state.count`
        countAlias: 'count',
    
        // 为了能够使用 `this` 获取局部状态,必须使用常规函数
        countPlusLocalState (state) {
          return state.count + this.localCount
        }
      })
    }
    

    先说说mapState的作用,返回一个对象。
    什么样的对象

    {    
        cout: xxx //第一个
        coutAlias:xxx //第二个
        countPlusLocalState(state){xxxx}//第三个
    }
    

    我尽可能说详细一点,

    export default {
      computed: mapState({
       /*根据箭头函数,生成的函数为
       function(state){ return state.count},
       所以count的值最后是state.count。count是个值。*/
        count: state => state.count,
        /* countAlias只是个名字,与上面的count没有一毛钱关系,我们的大前提,
    是使用的vuex里的mapstate,所以,只要我们给一个store里的属性名,
    mapstate就会把属性名对应的属性返回给我们,
    并用我们提供的新名字。所以,countAliae就对应的state里的count。
       */
        countAlias: 'count',
    /*注意,我强调一下,如果你只给属性名,不给别名,那么就会按原属性名返回。  比如就是一个孤零零的      'a'    你调用的时候也要调a就完事了,上面的是调countAlias  */
    
        // 为了能够使用 `this` 获取局部状态,必须使用常规函数
        /*  箭头函数基础,亲,如果你看不懂为什么要这样,请去参考下ES6的箭头函数 */
        countPlusLocalState (state) {
          return state.count + this.localCount
        }
      })
    }
    

    接下来讲mapState的第二个作用:偷懒。
    突然出现了一个...mapState,这个...就是对象展开运算符,是ES6里的,可关键是为什么搞了这么一个另人费解的东西,有什么用呢?
    答:并没有什么卵用还是有点用的,比如,你的compute属性里想用store里返回的东西,一共用5个!就可以用mapstate传5个属性名进去把五个搞出来再添加到compute里。
    比如

    computed:{
                        a1:function(){xxxxx}
    }
    

    可是你需要a2到a6,如果一个一个的写的话,就会这样:

      computed:{
                        a1:function(){xxxxx},
                        a2:function(){return this.$store.a2},
                        a3:function(){return this.$store.a3},
                        .......
    }
    

    有了这个...

    //注意,我再强调一下,如果你只给属性名,不给别名,
    那么就会按原属性名返回。所以可以这么写:
    compute:{
              a1:function(){xxxxx},
            ...mapState({'a2','a3','a4','a5','a6'})
    }
    

    然后就全有了,和上面的作用一样,看吧,偷懒就完事了。

    2 Getter

    首先要审明下,做为读书人的我,也是会骂人的!!!!
    之前很多程序员总是有些很奇怪的爱好,比如他母亲的Getter。这个Getter一见到名字就是想起了被java支配的恐惧,所以不说了 所以一定要好好理解!
    vuex里的getter,是在store里的一个东西,为什么需要它呢?比如,
    this.$store.state.todos.filter(todo => todo.done).length这一句分别在十个函数里都出现了,就可以把他考虑做成一个getter函数。
    把官网的例子搞了过来

    const store = new Vuex.Store({
      state: {
        todos: [
          { id: 1, text: '...', done: true },
          { id: 2, text: '...', done: false }
        ]
      },
      getters: {
        doneTodos: state => {
          return state.todos.filter(todo => todo.done)
        }
      }
    })
    

    Getter 会暴露为 store.getters 对象:

    store.getters.doneTodos // 运行会得到 [{ id: 1, text: '...', done: true }]
    

    Getter接受其他 getter 作为第二个参数,很奇怪是吧,但是有时候需要用到getters里的别的函数。(这里定义时不支持this直接拿)

    getters: {
      // ...
      doneTodosCount: (state, getters) => {
        return getters.doneTodos.length
      }
    }
    store.getters.doneTodosCount // 得到1
    

    在任何组件中(只要是上一级组件有store的)都可以使用:

    computed: {
      doneTodosCount () {
        return this.$store.getters.doneTodosCount
      }
    }
    

    getter 为什么好好的要返回一个函数?因为你返回了一个函数,还可以给函数传值,让他查些什么玩意,难道你们没有发现上面的例子都只是没有传我们自定义的参数的?现在发现了就行~

    getters: {
      // ...
      getTodoById: (state) => (id) => {
        return state.todos.find(todo => todo.id === id)
      }
    }
    
    store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
    

    还有剩下的mapGetters,嘿嘿,和上面的mapState一样,返回属性的规则也一样:1 别名 2 偷懒

    3 Mutation

    如果 1与2都是获取数据展示的话,那么3与4就是修改完通知了~
    搞一手官网:

    更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。
    一个最基本的例子:

    const store = new Vuex.Store({
      state: {
        count: 1
      },
      mutations: {
        increment (state) {
          // 变更状态
          state.count++
        }
      }
    })
    //使用:
    store.commit('increment')
    //注意,这里的increment 只能通过store.commit('increment')才能触发
    

    载荷,传事件的时候同时传个参数

    既然能传一个字符串的事件类型的increment,那么一定会有别的更复杂的使用方法。妥妥的,commit里还能搞个别的东西传过去,这个就叫:载荷(payload)(我觉得可以理解成: 装备)

    mutations: {
      increment (state, payload) {
        state.count += payload.amount
      }
    //提交的风格有两种(还是从官网上搞的)
    //风格1:
    store.commit('increment', {
      amount: 10
    })
    //风格2:(个人觉得这个逼格更高一点点)
    store.commit({
      type: 'increment',
      amount: 10
    })
    

    使用payload也是有条件的,

    • 提前在你的 store 中初始化好所有所需属性
    • 在对象上添加新属性时,应该更新这个属性,而不是再用一个新的对象
      • 使用 Vue.set(obj, 'newProp', 123)
      • 以新对象替换老对象。state.obj = { ...state.obj, newProp: 123 }

    使用更短的名字使用Mutation&在组件中使用Mutation

    你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。 (官网原话)

    import { mapMutations } from 'vuex'
    
    export default {
      // ...
      methods: {
        ...mapMutations([
          'increment', /*将 `this.increment()` 映射为 
    `this.$store.commit('increment')`*/
    
          // `mapMutations` 也支持载荷:
          'incrementBy' /*将 `this.incrementBy(amount)` 映射为
     `this.$store.commit('incrementBy', amount)`*/
        ]),
        ...mapMutations({
          add: 'increment' /* 将 `this.add()` 映射为 
    `this.$store.commit('increment')`*/
        })
      }
    }
    

    然后官网也提到了使用mutations的注意事项
    不能使用异步函数(等待时间不能太长的函数,什么网络连接,读写文件,都不是异步函数)

    如果你的程序中,有很多'increment'这种事件,推荐用常量保存后放在一个文件中,就像这样:

    //文件名: mutation-types.js
    export const GO_TO_PICS = 'GO_TO_PICS'
    export const GO_TO_NEWS = 'GO_TO_NEWS'
    export const GO_TO_WE = 'GO_TO_WE'
    

    4 Action

    离4这个标题不远处,你依稀可以看到,mutations不能使用异步函数,
    Action就是提交异步事件的~
    Action 提交的是 mutation,Action提交的是Action ,提交的是 mutation。重要的事件要说三遍。所以,Action是在mutation外又加了一层,看一个简单的例子。

    const store = new Vuex.Store({
      state: {
        count: 0
      },
      mutations: {
        increment (state) {
          state.count++
        }
      },
      actions: {
        increment (context) {
          context.commit('increment')
        }
      }
    })
    

    context是什么玩意?有什么用?
    答:他是和 store 实例具有相同方法和属性的 context 对象。作用是[个人猜测,未验证]context对象作为store的辅助,也许是store的一些操作一定要通过context进行。

    调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。

    少写一点(引用自网站,纯语法):

    实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候):

    actions: {
    //这是ES6的语法
    /*举个例子:{ blowUp } = { blowUp: 10 }; 结果:{blowUp:10}
    从blowUp中取一个叫blowUp的属性。
    参数本来是传过来的context,从context中取一个commit的东西,得到  { commit : context.commit };
    挺另人头大的。
    */
      increment ({ commit }) {
        commit('increment')
      }
    }
    
    

    使用action
    store.dispatch('increment')
    但是最主要是异步调用:

    actions: {
      incrementAsync ({ commit }) {
        setTimeout(() => {
          commit('increment')
        }, 1000)
      }
    }
    

    同样的支持载荷(payload,装备)

    // 以载荷形式分发
    store.dispatch('incrementAsync', {
      amount: 10
    })
    
    // 以对象形式分发
    store.dispatch({
      type: 'incrementAsync',
      amount: 10
    })
    

    官网上的一个更复杂的例子-购物车:

    actions: {
    // es6的语法,从context中取commit与state
      checkout ({ commit, state }, products) {
        // 把当前购物车的物品备份起来 ...对象展开
        const savedCartItems = [...state.cart.added]
        // 发出结账请求,然后乐观地清空购物车
        commit(types.CHECKOUT_REQUEST)
        // 购物 API 接受一个成功回调和一个失败回调
        shop.buyProducts(
          products,
          // 成功操作
          () => commit(types.CHECKOUT_SUCCESS),
          // 失败操作
          () => commit(types.CHECKOUT_FAILURE, savedCartItems)
        )
      }
    }
    

    上面只是定义,还有在组件中使用action的方法,不要忘记这个。
    关于mapActions,作用和定义你们懂得,下面用的官网的例子:

    你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):

    import { mapActions } from 'vuex'
    
    export default {
      // ...
      methods: {
        ...mapActions([
          'increment', /* 将 `this.increment()` 映射为
     `this.$store.dispatch('increment')`*/
    
          // `mapActions` 也支持载荷:
          'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
        ]),
        ...mapActions({
          add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
        })
      }
    }
    
    

    组合 Action(全盘搬的官网,这个部分很好理解)

    Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?

    首先,你需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:

    actions: {
      actionA ({ commit }) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            commit('someMutation')
            resolve()
          }, 1000)
        })
      }
    }
    
    

    现在你可以:

    store.dispatch('actionA').then(() => {
      // ...
    })
    
    

    在另外一个 action 中也可以:

    actions: {
      // ...
      actionB ({ dispatch, commit }) {
        return dispatch('actionA').then(() => {
          commit('someOtherMutation')
        })
      }
    }
    
    

    最后,如果我们利用 async / await,我们可以如下组合 action:

    // 假设 getData() 和 getOtherData() 返回的是 Promise
    
    actions: {
      async actionA ({ commit }) {
        commit('gotData', await getData())
      },
      async actionB ({ dispatch, commit }) {
        await dispatch('actionA') // 等待 actionA 完成
        commit('gotOtherData', await getOtherData())
      }
    }
    
    

    一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

    5 Module

    这个部分以后再说吧,想了解的可以去官网看看先。

    相关文章

      网友评论

        本文标题:VUE组件系统极速入门2--vuex

        本文链接:https://www.haomeiwen.com/subject/hnpczxtx.html