十分钟了解vuex

244 阅读6分钟

什么是vuex?

vuex是专为 Vue.js 应用程序开发的状态管理模式,它可以对状态进行集中管理,并且状态是响应式的,在状态改变时,会同时改变其他页面的该数据的状态。vuex其实是一个对象。


什么时候用vuex?

当多个界面需要共享同一个状态时使用。

例如,登录状态,用户需要登录才能使用页面,多个页面都是需要登录状态的

例如,地理位置信息,美团饿了么,首页需要位置信息,商家界面,送餐界面都需要位置信息

例如,收藏,购物车,多个页面都有收藏按钮,收藏加购的状态也应该是响应的

如果只是父子组件之间需要共同的状态,不应该使用vuex,这样反而会导致vuex变得臃肿,不方便管理。


vuex的使用方法

  1. 安装vuex:npm install vuex --save
  2. 引入和安装

// store文件夹中的index.js
// 1.导入vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 2.安装vuex
Vue.use(Vuex)

// 创建vuex实例(注意是使用store)
const store = new Vuex.Store({
    // 4. 配置vuex
    state: {}
})

// 导出vuex实例store
export default store

// main.js
// 导入store 挂载到vue实例上
import store from './store'
new Vue({
  // 挂载后,内部会生成$store
  store,
  render: h => h(App)
}).$mount('#app')


vuex使用图例

Actions:用来监测异步操作

Mutations:用来监测同步操作

State:vuex.Store中的state,用来存放多个界面公共使用的状态(数据)

Devtools:监测同步数据的改变

Backend API:后端接口


执行流程:

state中的数据可以渲染到vue的组件中,但是组件要更改state中的数据的话,需要通过actions,actions连接着后端API,主要处理异步的操作,当actions传入mutations时,会转为同步操作,此时,这个同步操作,就会被devtools检测到,我们就可以知道是哪个组件更改了数据。

如果我们直接跳过前面的过程修改state,那么devtools就无法监测到是哪个组件更改了数据。如果是同步的操作,可以直接通过mutations修改,devtools是可以监测到的。异步的操作必须通过actions,否则devtools无法监测到异步操作。


在vuex中改变数据图例



vuex的核心概念

1. State

state用于储存多个界面公共使用的状态(数据)

state是单一状态树,也叫做单一数据源。意思是只使用一个state管理全部的数据。也就是说,每个应用只包含一个vuex的实例。

在state中定义的数据,都会被放入响应式系统。也就是说,如果我们在之后修改了state中的数据,也会动态地响应到页面上。如果数据是后添加的,不会有响应式,需要使用特定的,vue包装好的响应式方法。

state中状态的获取

this.$store.state.count


2. Getter

getter可以理解为state的计算属性,它和vue中的计算属性一样,可以返回计算后的结果,并且这个结果也会被缓存起来,只有发生改变才会重新计算。

getter数据的获取:属性 和 方法,区别在于一个直接返回计算后的值,一个返回函数

1. 通过属性来获取,是存在缓存的,但是不能对数据进行进一步的读取。在这个例子中,不能通过TodoList读取到TodoList中的id/text/done等。

<template>
  <div id="app">
    <!-- 显示整个对象,无法进一步获取TodoList中的值 -->
    {{this.$store.getters.TodoList}}
  </div>
</template>

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


2. 通过方法来获取,不存在缓存,每次都会调用一次方法,可以通过TodoList读取到TodoList中的id/text/done等。

<template>
  <div id="app">
    <!-- 显示整个对象 -->
    {{this.$store.getters.getTodoById(2)}}
    <!-- 显示getTodoById中的text的值456 -->
    {{this.$store.getters.getTodoById(2).text}}
  </div>
</template>

const store = new Vuex.Store({
  state: {
    count: 0,
    todos: [
      { id: 1, text: '123', done: true },
      { id: 2, text: '456', done: false }
    ]
  },
  getters: {
    // getter返回方法的简写方式
    getTodoById: (state) => (id) => {
      return state.todos.find(todo => todo.id === id)
    },
    // getter返回方法的完整方式
    getTodoById (state) {
      return function (id) {
        return state.todos.find(todo => todo.id === id)
      }
    }
  }
})


3. Mutation

mutation用于更改同步的状态。

每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)

事件类型指的是mutation中的函数名,回调函数则是mutation中函数的执行内容,由于这个函数是在commit之后触发的,所以是一个回调函数。

export default {
  name: 'App',
  created () {
    // 触发mutation中的increment方法,执行该回调,这个increment就是事件类型(type)
    this.$store.commit('increment')
  }
}

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


mutation的参数传递,这个传递的参数叫做载荷——payload,不用在意,这只是一个名字而已。

// 1.传递一个参数
this.$store.commit('increment', 10)

// 2.传递多个参数——对象
this.$store.commit('increment', {
	count: 10,
    newName: 'mia'
})

// 3.对象风格的提交方式——就是将事件类型也放入对象中传递
this.$store.commit({
	type: 'increment',
    count: 10
})


传递的参数如何使用呢?只要在state后面添加一个payload形参就可以接收到了。

mutations: {
  increment (state, payload) {
    // 如果只穿了一个值,那就直接使用
    state.count += payload
    // 如果传入了对象,就按照对象的方法取值即可
    state.count += payload.count
  }
}


  • 需要注意的是,由于vuex是响应式的,所以mutation也要遵循vue的规则,只有这样才能保证在mutation更改了数据后,该数据能够响应式地发生改变。
  • 和vue只有一开始在data中声明的数据才会变成响应式的类似,mutation中更改的值,也最好事先在state中进行声明,如果未声明,和vue中一样,可以通过Vue.set()进行添加对象的新属性,或者使用新对象替换老对象等。
  • 另外,state中的值,即使不通过mutation,页面中也是会发生改变的,但是这种改变不会被devtools检测到,不利于我们的开发。类似的,如果mutation中是异步的操作,当我们触发mutation中的函数时,异步的操作还未执行,那么devtools就无法检测到这个数据的变化,因此在mutation中,一定是同步的操作。那该如何执行异步操作并被有效检测到数据的变化呢?那就要使用到Action了。


4. Action

action用于异步状态的更改。

action同样可以传递参数,和mutation中是一样的,不再赘述。

与mutation有所不同的是,action接收的第一个参数是context,context 是与 store 实例具有相同方法和属性的对象。因此可以使用解构赋值或者对象的方法选取到getter、state,也可以直接拿到store对象的commit、dispatch方法。

action需要先通过commit提交mutation,接着可以通过dispatch触发action中的函数。dispatch 的好处是可以处理 被触发的 action 中的函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise。方便了我们对数据的处理。

export default {
  name: 'App',
  created () {
    this.$store.dispatch('someMutation').then((res) =>{})
  }
}

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


5. Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。由于这个原因,就有了module,module可以让我们将状态分为一个个模块,便于代码的管理。

每个模块都有自己的state、mutations、actions、getters属性,也可以在模块中进一步嵌套模块。模块内都属于局部状态,对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。对于模块内部的 action,context也是局部的,根节点状态则为 context.rootState。rootState对象中包含了所有模块的state数据,还有一个rootGetters对象,储存了所有getter中的数据。

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: {
     actionFn (context) {
      console.log(context.rootState, context.rootGetters)
    }
  },
  getters: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA
  }
})

this.$store.state.a // -> moduleA 的state
this.$store.getters // -> moduleA 的getters

默认情况下,这些模块都是都是注册在全局状态的,如果希望模块注册在局部,可以添加namespaced: true。

const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true,
      state: () => ({ ... }),
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      }
    }
  }
})

辅助函数 map...

vuex中的每一个核心概念都有一个辅助函数,分别是mapState、mapGetters、mapMutations、mapActions、mapModules。
我理解的辅助函数,是用于处理vuex中的状态/方法与计算属性名的映射关系。辅助函数帮助我们生成计算属性,简化了我们的代码。
下面以mapState为例:
<template>
  <div id="app">
    {{count}}
    <hr/>
    {{countAlias}}
  </div>
</template>

// 引入mapState
import { mapState } from 'vuex'

// 1. 对象方法,自定义名称和state中的属性一一对应
computed: mapState({
  // 箭头函数可使代码更简练
  count: state => state.count,

  // 传字符串参数 'count' 等同于 `state => state.count`
  countAlias: 'count'
})

// 2. 数组方法,适用于名称和state中的属性名一模一样的情况
computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])