Vuex 使用教程


共计 8827 个字符,预计需要花费 23 分钟才能阅读完成。

1. 简介

Vuex 是一个 Vue 的状态管理工具,状态就是数据。它的使用场景是多个组件共同维护一份数据(如购物车),或者多个组件使用同一个状态(个人信息)。

其实就是集中式的管理数据,如下图所示:

Vuex 使用教程

/>

官方文档:https://vuex.vuejs.org/zh/

2. 基本使用

vue2 中使用 vuex 分为以下几个步骤:

  • 安装 vuex
  • 引入 vuex
  • 注册 vuex 插件
  • 创建 Vuex Store 实例
  • Vuex Store 注入 Vue 实例
npm i vuex@3

接下来要创建 Vuex Store,一般会在 src目录下新建一个 store/index.js 文件。

// 导入 vuex
import Vue from 'vue'
import Vuex from 'vuex'

// vuex 也是 vue 的插件, 需要 use 一下, 进行插件的安装初始化
Vue.use(Vuex)

// 创建 Vuex Store 实例
export default new Vuex.Store({
    state: {
      count: 1
    }
})

main.js 中引入 Vuex Store,并注入。

import store from './router'

new Vue({
    el: '#app',
    components: { App },
    template: '<App/>',
    store
})

在组件中这样使用即可:

<template>
  <div id="app">
    <p>Count: {{ $store.state.count }}</p>
  </div>
</template>

<script>
  export default {
    name: 'HelloWorld'
  }
</script>

3. 核心概念

3.1 State

Vuex 使用单一状态树,这意味着每个应用将仅仅包含一个 store 实例。存储在 Vuex 中的数据和 Vue 实例中的 data 遵循相同的规则,例如状态对象必须是纯粹 (plain) 的。

Vue Store 定义如下:

// 导入 vuex
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
      count: 1
    }
})

组件中可以通过获取 Vuex Store 的实例 $store 获取数据。

<template>
  <div id="app">
    <p>Count: {{ $store.state.count }}</p>
  </div>
</template>

<script>
  export default {
    name: 'HelloWorld'
  }
</script>

这里需要注意的是,不要将 state 数据变为 data 数据,这样会导致和其他组件状态不统一。更好的做法是,将其变成一个计算属性。

<template>
  <div id="app">
    <p>Count: {{ count }}</p>
  </div>
</template>

<script>
  export default {
    name: 'HelloWorld',
    computed: {
      count () {
        return this.$store.state.count
      }
    }
  }
</script>

计算属性虽好,但是如果要获取很多个状态时,代码显得非常冗余。这个时候可以使用 mapState 辅助函数获取。

mapState 使用数组的方式获取多个状态,方式如下:

<template>
  <div id="app">
    <p>Count: {{ count }}</p>
  </div>
</template>

<script>
  import { mapState } from 'vuex'

  export default {
    name: 'HelloWorld',
    computed: mapState(['count', 'num']
    //  相当于
    // count(){return this.$store.state.count}
    // num(){return this.$store.state.num}
  }
</script>

但是当组件有其他计算属性时,通过数组就不太合理了,这时候需要 es6 中的展开运算符:

<template>
  <div id="app">
    <p>Count: {{ count }}</p>
  </div>
</template>

<script>
  import { mapState } from 'vuex'

  export default {
    name: 'HelloWorld',
    data() {
      return {
        value: 17
      }
    },
    computed: {
      doubleValue () {
        return this.value * 2
      },
      ...mapState(['count','num'])
    }
  }
</script>

3.2 getters

getters 相当于 vue 中的计算属性,通过 getters 进一步处理,得到想要的值,而且允许传参,第一个参数就是 state。

Vue Store 定义如下:

// 导入 vuex
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
      count: 1
    },
    getters: {
      doubleCount(state) {
        return state.count * 2
      }
    }
})

组件中使用:

<template>
  <div id="app">
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
  </div>
</template>

<script>
  export default {
    name: 'HelloWorld',
    computed: {
      // 上一节中提到的
      ...mapState(['count']),
      doubleCount() {
        return this.$store.getters.doubleCount
      }
    }
  }
</script>

还可以通过 mapGetters 辅助函数帮助我们生成计算属性:

<template>
  <div id="app">
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
  </div>
</template>

<script>
  import { mapState, mapGetters } from 'vuex'

  export default {
    name: 'HelloWorld',
    computed: {
      ...mapState(['count']),
      ...mapGetters(['doubleCount'])
    },
  }
</script>

3.3 mutation

mutation 类似于 vue 中的 methods,它是更改 Vuex 的 store 中的状态的唯一方法。并且,mutation 中的方法都必须是同步的。

每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是实际进行状态更改的地方,并且它会接受 state 作为第一个参数。

需要注意的是,不能直接调用一个 mutation 处理函数,而需要以相应的 type 调用 store.commit 方法。

Vue Store 定义如下:

// 导入 vuex
import Vue from 'vue'
import Vuex from 'vuex'

// vuex 也是 vue 的插件, 需要 use 一下, 进行插件的安装初始化
Vue.use(Vuex)

// 创建 Vuex Store 实例
export default new Vuex.Store({
    state: {
      count: 1
    },
    getters: {
      doubleCount(state) {
        return state.count * 2
      }
    }
    mutations: {
      increment(state) {
        state.count++
      }
    }
})

组件中使用:

<template>
  <div id="app">
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">同步+1</button>
  </div>
</template>

<script>
  import { mapState, mapGetters } from 'vuex'

  export default {
    name: 'HelloWorld',
    data() {
      return {
        value: 17
      }
    },
    computed: {
      ...mapState(['count']),
      ...mapGetters(['doubleCount'])
    }, 
    methods: {
      increment() {
        this.$store.commit('increment')
      }
    }
  }
</script>

同样可以使用 mapMutations 简化:

<template>
  <div id="app">
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">同步+1</button>
    <button @click="add">同步+1</button>
  </div>
</template>

<script>
  import { mapState, mapGetters, mapMutations } from 'vuex'

  export default {
    name: 'HelloWorld',
    data() {
      return {
        value: 17
      }
    },
    computed: {
      ...mapState(['count']),
      ...mapGetters(['doubleCount'])
    }, 
    methods: {
      ...mapMutations(['increment']),
      // 将 this.add() 映射为 this.$store.commit('increment')
      ...mapMutations({
        add: 'increment'
      }),
    }
  }
</script>

可以向 store.commit 传入额外的参数:

// 定义
mutations: {
  increment (state, n) {
    state.count += n
  }
}
// 使用
store.commit('increment', 10)

大多数情况下,参数应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:

// 定义
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
// 使用
store.commit('increment', {
  amount: 10
})

3.4 action

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 state 和 getters。然而,context 并不是 store 实例本身。

Vue Store 定义如下:

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

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
      count: 1
    },
    getters: {
      doubleCount(state) {
        return state.count * 2
      }
    },
    mutations: {
      increment(state) {
        state.count++
      }
    },
    actions: {
      incrementAsync(context) {
        setTimeout(() => {
          context.commit('increment')
        }, 1000)
      }
    }
})

组件中使用:

<template>
  <div id="app">
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">同步+1</button>
    <button @click="add">同步+1</button>
    <button @click="incrementAsync">异步+1</button>
    <button @click="addAsync">异步+1</button>
  </div>
</template>

<script>
  import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'

  export default {
    name: 'HelloWorld',
    computed: {
      ...mapState(['count']),
      ...mapGetters(['doubleCount'])
    },
    methods: {
      ...mapMutations(['increment']),
      ...mapMutations({
        add: 'increment'
      }),
      ...mapActions(['incrementAsync']),
      ...mapActions({
        addAsync: 'incrementAsync'
      })
    }
  }
</script>

actions 支持同样接受参数,还能支持对象形式分发:

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

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

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

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

state: {
  count: 1
},
mutations: {
  increment(state) {
    state.count++
  }
},
actions: {
  incrementAsync({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('increment')
        resolve()
      }, 1000)
    })
  }
}

现在可以使用:

methods: {
  incrementAsync() {
    this.$store.dispatch('incrementAsync')
    .then(() => {
      console.log('incrementAsync 完成')
    })
  }
}

还可以:

state: {
  count: 1
},
mutations: {
  increment(state) {
    state.count++
  }
},
actions: {
  incrementAsync({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('increment')
        resolve()
      }, 1000)
    })
  },

  // 调用两次 increment
  incrementAsync2({ dispatch, commit }) {
    return dispatch('incrementAsync').then(() => {
      commit('increment')
    })
  }
}

4. 模块化开发

4.1 简单使用

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。

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

Vue.use(Vuex)

const counter = {
    state: {
        count: 0
    }
}
const user = {
    state: {
        name: 'zhangsan'
    }
}

const modules = {
    counter,
    user
}

export default new Vuex.Store({
    modules
})

在组件中使用:

<template>
  <div id="app">
    <p>Count: {{ $store.state.counter.count }}</p>
    <p>Name: {{ $store.state.user.name }}</p>
  </div>
</template>

4.2 模块抽取

其实就是将模块抽离到单独的文件中,然后通过 import 引入。

counter.js

const state = {
    count: 1
}

const getters = {
    doubleCount: state => state.count * 2
}

const mutations = {
    increment(state) {
        state.count++
    }
}

const actions = {
    incrementAsync(context) {
        setTimeout(() => {
            context.commit('increment')
        }, 1000)
    }
}

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

user.js

const state = {
    name: "zhangsan"
}

const getters = {
    upperCaseName(state) {
        return state.name.toUpperCase()
    }
}

const mutations = {
    changeName(state) {
        state.name = 'lisi'
    }
}

const actions = {
    changeNameAsync({ commit }) {
        setTimeout(() => {
            commit("changeName")
        }, 1000)
    }
}

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

当在一个模块中设置了 namespaced: true 时,该模块的所有 action、mutation 和 getter 都会被限定在该命名空间下。这意味着在组件中访问这些模块的内容时,需要指定命名空间

如果没有设置 namespaced: true,则所有模块的 action、mutation 和 getter 都会注册在全局命名空间下,可能导致命名冲突和混乱。

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import counter from './modules/counter'

Vue.use(Vuex)

const modules = {
    counter,
    user
}

console.log(modules)

export default new Vuex.Store({
    modules
})

组件中使用:

<template>
  <div id="app">
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">同步+1</button>
    <button @click="incrementAsync">异步+1</button>
    <p>Name: {{ name }}</p>
    <p>UpperCase Name: {{ upperCaseName }}</p>
    <button @click="changeName">修改名称</button>
    <button @click="changeNameAsync">异步修改名称</button>
  </div>
</template>

<script>
  import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
  export default {
    name: 'HelloWorld',
    data() {
      return {
        value: 17
      }
    },
    created() {
      console.log(this.$store)
    },
    computed: {
        ...mapState({
          count: state => state.counter.count,
          name: state => state.user.name
        }),

        // 方式一
        // ...mapGetters('counter', [ 'doubleCount' ]),
        // ...mapGetters('user', [ 'upperCaseName' ]),

        // 方式二
        ...mapGetters({
          doubleCount: 'counter/doubleCount',
          upperCaseName: 'user/upperCaseName'
        })
    }, 
    methods: {

       // 方式一
      // ...mapMutations('counter', ['increment']),
      // ...mapMutations('user', ['changeName']),
      //...mapActions('counter', ['incrementAsync']),
      //...mapActions('user', ['changeNameAsync'])

      // 方式二
      ...mapMutations({
        increment: 'counter/increment',
        changeName: 'user/changeName'
      }),
      ...mapActions({
        incrementAsync: 'counter/incrementAsync',
        changeNameAsync: 'user/changeNameAsync'
      })

    }
  }
</script>

提醒:本文发布于115天前,文中所关联的信息可能已发生改变,请知悉!

Tips:清朝云网络工作室

阅读剩余
THE END