初识Vuex

现如今vue2.0的组件化开发思想已经深入人心,但是组件越是细化,组件之间的交流就显得苍白无力。若组件之间有某种“血缘”关系还好说(父子组件),毫无层级关系的组件,沟通交流起来确实存在“代沟”。还好有vuex状态管理,让所有状态都在托管在一个地方,状态管理起来也就很简单清晰了,状态管理可以分为四个模块:state、getter、mutation、action。下面我简单分享下它的使用方法。
1、先用vue-cli搭建一个webpack项目
2、在项目中安装vuex:

npm install vuex --save

3、在src文件夹下创建一个store文件夹里面创建一个store.js:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

// 先写一个简单的vuex
export default new Vuex.Store({
  state: {
    num: 0
  },
  mutations: {
    add(state) {
      state.num++
    }
  }
})

4、在main.js中import你的store.js

import store from './store/store'
new Vue({
   el: '#app',
   router,
   store, // 可以让任何组件通过this.$store获取vuex中的东西
   components: { App },
   template: '<App/>'
})

5、简单的vuex已经创建好,现在可以在任何组件获取到vuex中的状态:
在HelloWorld组件中用一下:

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <h2>vuex中的num状态为:{{h2}}</h2>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'vuex示例',
      h2: this.$store.state.num // 直接用this.$store就可以取到整个状态树的信息
    }
  }
}
</script>

这里写图片描述

上图为以上步骤的效果
注意:上面那样(在data属性里获取store的state)会有个问题,在this.$store.commit(‘add’)的时候(即去提交状态的时候),视图并没有响应(这里是让num++),num一直为0。这是为什么呢?这就要说下vue中data属性和computed属性的区别:
1)、data 中的内容只会在 created 钩子触发前初始化一次,则data中的数据,仅仅是对state的一个引用。data 中内容依赖变更时(state更变),data 属性并不会变更(data并不会重新计算)
2)、computed 则是通过【依赖追踪】实现的,在 computed 求值时引用的 Vue 变量变化时,会触发对 computed 的重新计算。

一句话,就是因为computed会重新计算。

所以,请将获取的state的步骤放到computed属性中,为了变更state后可以反应到视图去:

computed: {
    h2() {
      return this.$store.state.num
    }
}

这里写图片描述
这样,点击按钮,视图的h2就会+1,以下是我HelloWorld的代码:

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <h2>vuex中的num状态为:{{h2}}</h2>
    <button @click="change">点我改变num</button>
  </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'vuex示例',
    }
  },
  computed: {
    h2() {
      return this.$store.state.num
    }
  },
  methods: {
    change() {
      this.$store.commit('add');
    }
  }
}
</script>

mapState 辅助函数

将store.js改下(多多一些state):

  state: {
    num: 0,
    total: 0, // 多加了一些state
    a: 1,
    b: 2
  }

如果HelloWorld组件中要得到所有state,只能在computed一个一个返回么?mapState 辅助函数给你一种简单的写法:
首先确保组件里:

import { mapState } from 'vuex'

则computed可以写成如下:

// mapState函数传入一个对象
computed: mapState({
    // 箭头函数可使代码更简练
    num: state => state.num,

    // 传字符串参数 'num' 等同于 `state => state.num`
    numAlias: 'num',

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.num+ this.msg
    }
  })

如果computed的名字与vuex中的state名字一样,则更加简洁了:

 // 注意mapState函数参数变成数组了
computed: mapState([
  // 映射 this.num为 store.state.num
  'num',
  'total',
  'a',
  'b'
])

效果:
这里写图片描述

以上的computed都是返回直接返回mapState的调用结果,如果,这个组件computed不止有vuex中的状态的话,就要使用ES6对象展开运算符… 将本组件的compute数据和vuex中的状态进行合并:

computed: {
  localComputed () { /* 本组件的computed数据 */ },
  // 使用对象展开运算符将此对象混入到外部对象中
  ...mapState({
    // ...
  })
}

6、如果有时候,你需要对你的state进行第二次运算再使用,比如需要把状态num扩大两倍,再减去十(num*2 -10),而且不仅仅是一个地方使用(别的组件也需要这样的值),就可以用vuex的第二大模块:Getter
store.js里:

state: {
    num: 0,
    total: 0,
    a: 1,
    b: 2
  },
  // 使用getters对状态进行二次封装
  getters: {
    numDone: state=> state.num*2 -10
  },

组件文件里:

computed: {
    numDone(){
      return this.$store.getters.numDone
    },
    ...mapState([
               'num',
               'total',
               'a',
               'b'
             ])
  },

效果:
这里写图片描述

Getter函数除了state还可以接收第二个参数:getter(次vuex中所有的getters集合),拿另一个getter做计算再返回:

getters: {
  numDone2: (state, getters) => {
    return Math.abs(getters.numDone)
  }
}
store.getters.numDone2 // -> 10

你也可以通过让 getter 返回一个函数,来实现给 getter 传参。

// store.js
state: {
    num: 0,
    total: 0,
    a: 1,
    b: 2,
    list: [
        {
           id: 1,
           name: 'aaa'
        },
        {
           id: 2,
           name: 'bbb'
        },
        {
           id: 3,
           name: 'ccc'
        },
    ]
  },
  getters: {
    getListItemById: (state) => (id) => {
        return state.list.find(item=> item.id === id)
      }
  },
// 组件中获得getter
store.getters.getListItemById(2) // -> { id: 2, name: 'bbb' }

同样地,Getter 也有mapGetters() 辅助函数,用法同mapState()

7、改变state像变量一样去赋值?可以是可以,但是代码太冗余太长不易阅读,所以vuex有一个Mutation的模块。它是用来专门更改 Vuex 的 store 中的状态的。

export default new Vuex.Store({
  state: {
    num: 0
  },
  mutations: { // 这里定义mutation里的事件回调函数
    add(state) { // 第一个参数为所有state集合
      state.num++
    }
  }
})

回调函数?官方文档是这样解释的:
Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type) 和 一个 回调函数 (handler)。而上面的add相当于事件注册,函数里面的代码等待add事件触发了再执行。
在任何组件中可以去通过comit函数去触发add事件:

this.$store.commit('add') // add为上诉说的字符串事件类型

提交到mutation中,从而改变num状态。

mutation的载荷:

mutations: {
  add(state, n) { // 载荷n作为第二个参数
    state.num += n
  }
}

调用:

this.$store.commit('add', 10)

如果载荷有多个,可以写成对象传进来(一般也是这样写):

this.$store.commit('add', {
  amount: 10,
  a: 1,
  b: 2
})

甚至为了好看,可以吧整个commit的参数写成对象形式:

this.$store.commit({
  type: 'add',
  amount: 10,
  a: 1,
  b: 2
})
mutations: {
  add(state, payload) { // payload为一个载荷集合
    console.log(payload) -> {amount: 10, a: 1, b: 2}
  }
}

通过mutation改变state要注意:
1)、最好提前在你的 store 中初始化好所有所需属性
2)、state为对象,需要添加新属性时,你应该:
Vue.set(obj, ‘newProp’, 123) 或者
state.obj = { …state.obj, newProp: 123 }
这样state才会是响应式的

使用mutation时还要注意一点:
mutation 必须是同步函数,例:add中不能够是一个异步函数

同样地,mutation也有mapMutations函数:

import { mapMutations } from 'vuex'

export default {
  // ...
  mounted() {
      this.add({
          a: 1,
          b: 2
      })
  },
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为`this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'add' // 将 `this.add(amount)` 映射为 `this.$store.commit('add', amount)`
    ]),
    ...mapMutations({
      fn: 'increment' // 将 `this.fn()` 映射为 `this.$store.commit('increment')`
    })
  }
}

8、想要异步改变state?那你该用vuex的Action

  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) { // 与 store 实例具有相同方法和属性的 context 对象
      context.commit('increment')
    }
  }

也可以使用ES6的对象解构来传参:

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}

异步操作:

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

action 通过提交mutation来改变state,那为啥不直接用mutation好了?因为:
1)可以在 action 内部执行异步操作,异步操作完成后改变state。而mutation只能够同步操作去更改state
2)我的理解:action可以多个mutation组合使用

Action 在组件中通过 store.dispatch 方法触发(同提交mutation类似):

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

这是官方文档的一个购物车的例子:
在action中集合了许多mutation,由异步操作的不同结果来决定什么时候调用哪个mutation去改变状态(分发多重 mutation)

actions: {
  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中去完成一些逻辑处理,也可以把它写成promise,resolve出来,在组件中dispatch后去写逻辑处理:

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

在组件中:

this.$store.dispatch('actionA').then(() => {
  // 以上的resolve()
})

在action也可以返回另外一个dispatch的promise

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

同样地,action也有mapActions方法,用法和mutation完全一致,会把组件的 methods 映射为 store.dispatch 调用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值