vuex知识点整理

217 阅读4分钟

概念理解

  • 什么是Vuex?

一个状态管理器,管理所有组件的状态。

  • 为什么使用Vuex?

1.多个组件依赖于同一个状态时。

2.不同的组件需要变更同一个状态。

以上都会导致组件之间的传参变得非常繁琐。

Vuex使用

  • 安装
npm i vuex --save
  • 项目目录
├── src/                     
│   ├── main.js                # 入口
│   ├── App.vue                # 根组件
│   ├── store                  # vuex配置
│   │   ├── index.js           # vuex入口              
  • index.js中创建实例并导出
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const debug = process.env.NODE_ENV !== 'production'

// 创建实例
export default new Vuex.Store({
   strict:debug,// 严格模式,不要再发布模式下启用严格模式!
   state:{
       
   },
   getter:{
       
   },
   mutations:{
       
   },
   actions:{
       
   }
})
  • 在mian.js中引入
import store from './store/index'
import Vue from 'vue';
import App from './App.vue';

const app = new Vue({
    store,
    render: h => h(App)
}).$mount('#app');

Vuex的核心属性

  • Vuex的5个核心属性是?

state, mutations, getters, actions, modules

一个简单的vuex已经搭建完成,接下来,我们依次来理解一下Vuex的5个属性,以便于我们知道要怎样使用。

state

这是状态管理中的存储库,所有的数据都会在这里被定义和设定初始值。

state:{
   count:0
}
  • 如何访问state?
<p>{{$store.state.count}}</p>

mutations

  • 获取state很容易,那么如何改变state?

改变state的唯一方式,就是commit mutation(提交 mutation)。简而言之,mutation就是封装了很多方法的一个集合,而这些方法可以用来改变state状态。

mutations:{
   // 加法
    add(state,n){
        state.count += n
    },
    // 减法
    reduce(state){
        state.count -= 1
    }
}
  • 如何使用mutations?

使用$store.commit提交。

<div @click="$store.commit('add',10)"></div>
<p>{{$store.state.count}}</p>

commit可以接收多个参数,第一个参数必须是moutations中定义的方法名,第二参数是外部传入方法中的参数。而方法add中,第一个参数,是全局的state状态,第二个参数则是外部传入参数。

如上代码,点击div元素,触发commit,接着触发moutations中的add函数,使得count与传入的参数相加,count状态改变。

值得注意的是,mutations中的函数,必须只能是同步函数。

getters

getters是对state做处理计算,可以把他看作在获取数据之前进行的一种再编辑,相当于对数据的一个过滤和加工。

  • 如何使用getters?
state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
 },
getters:{
    getCount:function(state){
        return state.count + 100
    },
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
}

页面组件中使用:

<p>{{$store.getters.getCount}}</p>
<p>{{$store.getters.doneTodos}}</p>

getters接收一个参数state:这是全局的state。

  • 怎么通过getters,实现在组件内通过特定条件来获取state的状态?
state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
 },
 getters:{
    getByID: (state) => {
       return function(id){
           return state.todos.filter(todo => todo.id === id)
       }
    }
 }

页面组件中使用:

computed:{
    getTodoById(){
        return this.$store.getters.getByID
    }
},
mounted:{
    this.getTodoById(2)
}

actions

actions也是方法的封装,同mutations功能是一样的,不同的是,actions可以异步改变状态,而mutations只能同步改变状态。

  • 如何使用actions?
actions:{
    addCount(context, params){
        context.commit('add', params)
    },
    reduceAction({ commit }, params){
       if(params){
           commit('reduce')
       }
    }
}

actions中的方法,可以接收多个参数,第一个参数是指当前上下文,第二个参数则是从外部提交传递过来的参数。

context指的是当前当前上下文,也就是store,其中包括它的许多属性:state, commit, dispath, getters等

可以用解构赋值{commit},将当前上下文的commit,指向参数,方便使用。

在页面组件中使用:

<div @click="$store.dispatch('addCount',10)"></div>
<div @click="$store.dispatch('reduceAction',true)"></div>

从上可以看出,action提交的是mutation,而不是直接改变状态。

  • action和mutation有何区别?

1.mutation直接改变状态,action是提交mutaion

2.mutation是同步操作,action可以异步操作。

3.提交方式,mutation使用commit,action使用dispatch

4.接收参数,mutation第一个参数是state,action第一个参数是context。

  • 如何使用action进行异步操作?
actions:{
    set_number_A({commit},data) {
        return new Promise((resolve,reject) => {
          setTimeout(() => {
              commit('set_num',data)
              resolve();
          },3000)  
        })
    }
}
// 组件中使用:
this.$store.dispatch('set_number_A',10).then(()=>{
    
})
  • Vuex中有两个action,分别是actionA和actionB,其内都是异步操作,在actionB要提交actionA,需在actionA处理结束再处理其它操作,怎么实现?

可以用ES6的 asyncawait 来实现。

actions:{
    async actionA({commit}){
        //...
    },
    async actionB({dispatch}){
        await dispatch ('actionA')//等待actionA完成
        // do something
    }
}

以上是vuex的四大核心属性,那么modules这个属性比较复杂,在下一小节分开讲,接着往下看。

模块化处理

  • 为什么要使用模块化?

使用单一的状态树时,应用的所有状态都会集中在一个比较大的对象上,如果应用非常复杂,store就会变得非常臃肿,所以,我们可以把store分割成模块,每个模块中都拥有自己的state, mutations, actions, getters,甚至是往下嵌套的子模块。

  • 如何使用模块化?

我们来调整一下我们的目录结构:

├── src/                     
│   ├── main.js                # 入口
│   ├── App.vue                # 根组件
│   ├── store                  # vuex配置
│   │   ├── modules            # vuex模块管理
│   │   │   ├── moduleA.js           # 模块A
│   │   │   ├── moduleB.js           # 模块B
│   │   ├── index.js           # vuex入口              

moduleA.js,moduleB.js中写入:

const state={
    //...
}
const getters={
    //...
}
const mutations={
    //...
}
const actions={
    //...
}
export default{
    state,
    getters,
    mutations,
    actions
}

index.js文件中重写:

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

import moduleA from './module/moduleA'
import moduleB from './module/moduleB'

const debug = process.env.NODE_ENV !== 'production'

// 创建实例
export default new Vuex.Store({
   strict:debug,
   modules:{
       moduleA,
       moduleB
   }
})
  • 在模块中,getter和mutation和action中怎么访问全局的state和getter?

在模块中,getter和mutation接收的第一个参数state,是模块的state,也就是局部的state。

mutations:{
   // 加法
    add(state,n){
        state.count += n
    }
}
getters:{
    getCount:function(state,getters,rootState,rootGetters){
        return state.count + 100
    }
}
actions:{
    addCount(context, params){
       context.commit('add',params)
    }
}

1.mutation中不可以访问全局state,只能访问模块state。

2.模块getters中,可接收多个参数,第一个state是模块的state,第二个参数是其他的getters,第三个参数rootState是全局的state,第四个参数rootGetters是全局的getters。

3.模块action中,第一个参数是context,其中,context.rootState访问全局state,context.rootGetters访问全局的getter。

  • 模块的命名空间

默认情况下,模块内部的action、mutation和getter是注册在全局命名空间,如果多个模块中action、mutation的命名是一样的,那么提交mutation、action时,将会触发所有模块中命名相同的mutation、action。

这样有太多的耦合,如果要使模块具有更高的封装度和复用性,可以通过添加namespaced: true 的方式使其成为带命名空间的模块。

export default{
    namespaced: true,
    state,
    getters,
    mutations,
    actions
}

在带命名空间的模块中提交全局的mutation和action

将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。

this.$store.dispatch('actionA', null, { root: true })
this.$store.commit('mutationA', null, { root: true })

在带命名空间的模块内注册全局的action

actions: {
    actionA: {
        root: true,
        handler (context, data) { ... }
    }
  }

在组件中提交modules中的带命名空间的moduleA中的mutationA

this.$store.commit('moduleA/mutationA',data)

辅助函数map

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
    }
  })
}

当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

computed: mapState(['count', 'text'])

由于 mapState 函数返回的是一个对象,在ES6的写法中,我们可以通过对象展开运算符,可以极大的简化写法:

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

mapGetters

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

如果你想将一个 getter 属性另取一个名字,使用对象形式:

mapGetters({
  // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
  doneCount: 'doneTodosCount'
})

mapMutations

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')`
    })
  }
}

mapActions

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')`
    })
  }
}

v-model的处理

使用带有 setter 的双向绑定计算属性:

// 组件中
<input v-model="message">

// 计算属性中
computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.commit('updateMessage', value)
    }
  }
}

// mutation中
mutations: {
  updateMessage (state, message) {
    state.obj.message = message
  }
}

严格模式的作用

在严格模式下,无论何时发生了状态变更且不是由 mutation函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。

在Vuex.Store 构造器选项中开启,如下

const store = new Vuex.Store({
    strict:true,
})

vuex插件

我们来调整一下我们的目录结构:

├── src/                     
│   ├── main.js                # 入口
│   ├── App.vue                # 根组件
│   ├── store                  # vuex配置
│   │   ├── plugins            # vuex插件管理
│   │   │   ├── logger.js           # 插件
│   │   ├── modules            # vuex模块管理
│   │   │   ├── moduleA.js           # 模块A
│   │   │   ├── moduleB.js           # 模块B
│   │   ├── index.js           # vuex入口              

Vuex插件就是一个函数,它接收 store 作为唯一参数。在Vuex.Store构造器选项plugins引入。

export default function createPlugin(param){
    return store =>{
        //...
    }
}

在store/plugin.js文件中写入

import createPlugin from './plugin.js'
const myPlugin = createPlugin()
const store = new Vuex.Store({
  // ...
  plugins: [myPlugin]
})

例子,引入持久化插件:

import createPersistedState from 'vuex-persistedstate' // 引入持久化插件
const store = new Vuex.Store({
  // ...
 plugins: [createPersistedState({ storage: window.sessionStorage })],
})