Vuexstatusmanagement

深入LearningVuex core concepts, statusmanagement流程 and using场景, MasterVueapplication statusmanagementsolution

1. Vuexoverview

Vuex is Vue.js 官方statusmanagementlibrary, 它providing了一个集in式storemanagementapplication 所 has component status, 并以相应 规则保证status以一种可预测 方式发生变化.

1.1 Vuex core concepts

  • State: storeapplicationstatus object
  • Getter: from Stateinfork出 new status
  • Mutation: modifyState 唯一方式, 必须 is synchronizationfunction
  • Action: processingasynchronousoperation, 可以submittingMutation
  • Module: 将Store分割成module, 便于management

1.2 Vuex 适用场景

Vuex适合用于in big 型application, 特别 is 当您遇 to 以 under circumstances时:

  • many 个component共享同一status
  • component间通信 complex , 涉及 many 层级 or 跨component通信
  • 需要记录status变化history, supportrevert/重做functions
  • 需要implementationstatus 持久化store

1.3 installationVuex

可以throughnpm or yarninstallationVuex:

# usingnpminstallation
npm install vuex@4

# usingyarninstallation
yarn add vuex@4

提示

Vuex 4 is for Vue 3 design , such as果你using is Vue 2, 应该installation Vuex 3: npm install vuex@3

2. basicStoreconfiguration

让我们开始creation一个basic Vuex Store.

2.1 creationStoreinstance

// src/store/index.js
import { createStore } from 'vuex'

// creationStoreinstance
const store = createStore({
  // status
  state() {
    return {
      count: 0,
      todos: [
        { id: 1, text: 'LearningVuex', done: false },
        { id: 2, text: '构建application', done: false }
      ]
    }
  },
  
  // Getter
  getters: {
    doneTodos: (state) => {
      return state.todos.filter(todo => todo.done)
    },
    doneTodosCount: (state, getters) => {
      return getters.doneTodos.length
    },
    getTodoById: (state) => (id) => {
      return state.todos.find(todo => todo.id === id)
    }
  },
  
  // Mutation
  mutations: {
    increment(state) {
      state.count++
    },
    decrement(state) {
      state.count--
    },
    addTodo(state, todo) {
      state.todos.push(todo)
    },
    toggleTodo(state, todoId) {
      const todo = state.todos.find(todo => todo.id === todoId)
      if (todo) {
        todo.done = !todo.done
      }
    }
  },
  
  // Action
  actions: {
    incrementAsync({ submitting }) {
      setTimeout(() => {
        submitting('increment')
      }, 1000)
    },
    addTodoAsync({ submitting }, todo) {
      return new Promise((resolve) => {
        setTimeout(() => {
          submitting('addTodo', todo)
          resolve()
        }, 1000)
      })
    }
  }
})

export default store

2.2 挂载Storeinstance

// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'

const app = createApp(App)

// 挂载Storeinstance
app.use(store)

app.mount('#app')

3. State

State is storeapplicationstatus object, 它 is Vuex Store core.

3.1 in componentin访问State

可以throughthis.$store.state in componentin访问State:

<template>
  <div>
    <p>当 before 计数: {{ $store.state.count }}</p>
    <ul>
      <li v-for="todo in $store.state.todos" :key="todo.id">
        {{ todo.text }}
      </li>
    </ul>
  </div>
</template>

3.2 usingmapState辅助function

可以usingmapState辅助function将Statemap to component 计算propertyin:

<template>
  <div>
    <p>当 before 计数: {{ count }}</p>
    <ul>
      <li v-for="todo in todos" :key="todo.id">
        {{ todo.text }}
      </li>
    </ul>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'CounterView',
  computed: {
    // objectunfold运算符将mapState返回 object混入computedobjectin
    ...mapState(['count', 'todos'])
    
    //  or 者usingobject形式, 自定义计算property名称
    /* ...mapState({
      counter: 'count',
      todoList: 'todos'
    }) */
  }
}
</script>

4. Getter

Getter用于 from Stateinfork出 new status, class似于component 计算property.

4.1 in componentin访问Getter

可以throughthis.$store.getters in componentin访问Getter:

<template>
  <div>
    <p>已completion 待办事项: {{ $store.getters.doneTodosCount }}</p>
    <ul>
      <li v-for="todo in $store.getters.doneTodos" :key="todo.id">
        {{ todo.text }}
      </li>
    </ul>
    <p>ID for 1 待办事项: {{ $store.getters.getTodoById(1).text }}</p>
  </div>
</template>

4.2 usingmapGetters辅助function

可以usingmapGetters辅助function将Gettermap to component 计算propertyin:

<template>
  <div>
    <p>已completion 待办事项: {{ doneTodosCount }}</p>
    <ul>
      <li v-for="todo in doneTodos" :key="todo.id">
        {{ todo.text }}
      </li>
    </ul>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  name: 'TodoView',
  computed: {
    // objectunfold运算符将mapGetters返回 object混入computedobjectin
    ...mapGetters(['doneTodos', 'doneTodosCount'])
    
    //  or 者usingobject形式, 自定义计算property名称
    /* ...mapGetters({
      completedTodos: 'doneTodos',
      completedCount: 'doneTodosCount'
    }) */
  }
}
</script>

5. Mutation

Mutation is modifyState 唯一方式, 它必须 is synchronizationfunction.

5.1 submittingMutation

可以throughthis.$store.submitting()methodsubmittingMutation:

<template>
  <div>
    <p>当 before 计数: {{ $store.state.count }}</p>
    <button @click="increment">增加</button>
    <button @click="decrement">reducing</button>
    <button @click="addTodo">添加待办事项</button>
  </div>
</template>

<script>
export default {
  name: 'CounterView',
  methods: {
    increment() {
      // submittingMutation
      this.$store.submitting('increment')
    },
    decrement() {
      this.$store.submitting('decrement')
    },
    addTodo() {
      // submittingMutation并传递parameter
      this.$store.submitting('addTodo', {
        id: 3,
        text: ' new  待办事项',
        done: false
      })
    }
  }
}
</script>

5.2 submitting风格

除了直接传递parameter out , 还可以usingobject风格submittingMutation:

// 直接传递parameter
this.$store.submitting('addTodo', {
  id: 3,
  text: ' new  待办事项',
  done: false
})

// object风格submitting
this.$store.submitting({
  type: 'addTodo',
  id: 3,
  text: ' new  待办事项',
  done: false
})

5.3 usingmapMutations辅助function

可以usingmapMutations辅助function将Mutationmap to component methodin:

<template>
  <div>
    <p>当 before 计数: {{ $store.state.count }}</p>
    <button @click="increment">增加</button>
    <button @click="decrement">reducing</button>
  </div>
</template>

<script>
import { mapMutations } from 'vuex'

export default {
  name: 'CounterView',
  methods: {
    // objectunfold运算符将mapMutations返回 object混入methodsobjectin
    ...mapMutations(['increment', 'decrement'])
    
    //  or 者usingobject形式, 自定义method名称
    /* ...mapMutations({
      add: 'increment',
      sub: 'decrement'
    }) */
  }
}
</script>

warning

Mutation必须 is synchronizationfunction, 不要 in Mutationin执行asynchronousoperation, asynchronousoperation应该放 in Actionin.

6. Action

Action用于processingasynchronousoperation, 可以submittingMutation.

6.1 分发Action

可以throughthis.$store.dispatch()method分发Action:

<template>
  <div>
    <p>当 before 计数: {{ $store.state.count }}</p>
    <button @click="incrementAsync">asynchronous增加</button>
    <button @click="addTodoAsync">asynchronous添加待办事项</button>
  </div>
</template>

<script>
export default {
  name: 'CounterView',
  methods: {
    incrementAsync() {
      // 分发Action
      this.$store.dispatch('incrementAsync')
    },
    async addTodoAsync() {
      // 分发Action并processingPromise
      await this.$store.dispatch('addTodoAsync', {
        id: 3,
        text: 'asynchronous添加 待办事项',
        done: false
      })
      console.log('待办事项添加成功')
    }
  }
}
</script>

6.2 Action parameter

Action接收一个 and Storeinstance具 has 相同method and property on under 文object, 以及一个可选 payloadparameter:

actions: {
  //  on  under 文objectpackage含submitting, state, gettersetc.property
  incrementAsync(context) {
    setTimeout(() => {
      context.submitting('increment')
    }, 1000)
  },
  
  // 解构 on  under 文object
  addTodoAsync({ submitting }, todo) {
    return new Promise((resolve) => {
      setTimeout(() => {
        submitting('addTodo', todo)
        resolve()
      }, 1000)
    })
  }
}

6.3 usingmapActions辅助function

可以usingmapActions辅助function将Actionmap to component methodin:

<template>
  <div>
    <p>当 before 计数: {{ $store.state.count }}</p>
    <button @click="incrementAsync">asynchronous增加</button>
  </div>
</template>

<script>
import { mapActions } from 'vuex'

export default {
  name: 'CounterView',
  methods: {
    // objectunfold运算符将mapActions返回 object混入methodsobjectin
    ...mapActions(['incrementAsync'])
    
    //  or 者usingobject形式, 自定义method名称
    /* ...mapActions({
      asyncAdd: 'incrementAsync'
    }) */
  }
}
</script>

7. Module

Module允许我们将Store分割成 many 个module, 每个module都 has 自己 State, Getter, Mutation and Action, 便于management big 型application status.

7.1 定义Module

// src/store/modules/counter.js
// 定义countermodule
const counterModule = {
  namespaced: true, // 开启namespace
  state() {
    return {
      count: 0
    }
  },
  getters: {
    doubleCount: (state) => {
      return state.count * 2
    }
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    incrementAsync({ submitting }) {
      setTimeout(() => {
        submitting('increment')
      }, 1000)
    }
  }
}

export default counterModule
// src/store/modules/todo.js
// 定义todomodule
const todoModule = {
  namespaced: true, // 开启namespace
  state() {
    return {
      todos: [
        { id: 1, text: 'LearningVuex', done: false },
        { id: 2, text: '构建application', done: false }
      ]
    }
  },
  getters: {
    doneTodos: (state) => {
      return state.todos.filter(todo => todo.done)
    }
  },
  mutations: {
    addTodo(state, todo) {
      state.todos.push(todo)
    }
  },
  actions: {
    addTodoAsync({ submitting }, todo) {
      setTimeout(() => {
        submitting('addTodo', todo)
      }, 1000)
    }
  }
}

export default todoModule

7.2 registerModule

// src/store/index.js
import { createStore } from 'vuex'
import counterModule from './modules/counter'
import todoModule from './modules/todo'

const store = createStore({
  modules: {
    counter: counterModule,
    todo: todoModule
  }
})

export default store

7.3 usingnamespaceModule

当开启namespace after , 访问module State, Getter, Mutation and Action需要加 on module名称:

// 访问module State
this.$store.state.counter.count

// 访问module Getter
this.$store.getters['counter/doubleCount']

// submittingmodule Mutation
this.$store.submitting('counter/increment')

// 分发module Action
this.$store.dispatch('counter/incrementAsync')

7.4 using辅助function访问namespaceModule

可以using辅助function访问namespaceModule, 需要指定module名称:

<template>
  <div>
    <p>当 before 计数: {{ count }}</p>
    <p>双倍计数: {{ doubleCount }}</p>
    <button @click="increment">增加</button>
    <button @click="incrementAsync">asynchronous增加</button>
  </div>
</template>

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

export default {
  name: 'CounterView',
  computed: {
    // usingmodule名称访问State
    ...mapState('counter', ['count']),
    // usingmodule名称访问Getter
    ...mapGetters('counter', ['doubleCount'])
  },
  methods: {
    // usingmodule名称访问Mutation
    ...mapMutations('counter', ['increment']),
    // usingmodule名称访问Action
    ...mapActions('counter', ['incrementAsync'])
  }
}
</script>

8. Vuex best practices

8.1 Statedesignbest practices

  • 单一datasources: 所 has applicationstatus集in in 一个Storein
  • State is 只读 : 唯一modifyState 方式 is submittingMutation
  • using常量命名Mutation: 便于maintenance and debug
  • module化design: 将 big 型application State分割成 many 个module

8.2 Mutationdesignbest practices

  • Mutation必须 is synchronizationfunction: asynchronousoperation应该放 in Actionin
  • Mutation应该 is atomicity : 每个Mutation只modify一个status
  • using常量命名Mutation: 便于maintenance and debug

8.3 Actiondesignbest practices

  • Action用于processingasynchronousoperation: such asAPIrequest, 定时器etc.
  • Action可以返回Promise: 便于processingasynchronous流程
  • Action可以组合: 一个Action可以分发另一个Action

8.4 Getterdesignbest practices

  • Getter用于forkstatus: such asfilterlist, 计算propertyetc.
  • Getter可以接收parameter: through返回functionimplementation
  • Getter可以组合: 一个Getter可以using另一个Getter

8.5 module化designbest practices

  • 开启namespace: 避免module间 命名conflict
  • 按functions划分module: such asusermodule, 商品module, 购物车moduleetc.
  • module in 部status独立: 每个module只management自己 status
  • module间通信: through根State or Actionimplementation

9. Vuex and Composition API

in Vue 3in, 可以usingComposition API and Vuex结合using:

<template>
  <div>
    <p>当 before 计数: {{ count }}</p>
    <p>双倍计数: {{ doubleCount }}</p>
    <button @click="increment">增加</button>
    <button @click="incrementAsync">asynchronous增加</button>
  </div>
</template>

<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'

// 获取Storeinstance
const store = useStore()

// 访问State
const count = computed(() => store.state.counter.count)

// 访问Getter
const doubleCount = computed(() => store.getters['counter/doubleCount'])

// submittingMutation
const increment = () => {
  store.submitting('counter/increment')
}

// 分发Action
const incrementAsync = () => {
  store.dispatch('counter/incrementAsync')
}
</script>

10. Vuex 替代solutions

除了Vuex out , 还 has 一些other statusmanagementsolutions可以选择:

10.1 Pinia

Pinia is Vue 3 官方statusmanagementlibrary, 它providing了更简洁 API, 更 good TypeScriptsupport, 以及更flexible architecture.

10.2 Composition API + provide/inject

for 于 small 型application, 可以usingComposition API结合provide/inject来managementstatus, 避免引入额 out 依赖.

10.3 event总线

for 于 simple component间通信, 可以usingevent总线, 但不适合management complex applicationstatus.

练习 1: basicVuex Store

  1. creation一个Vueproject, 并installationVuex.
  2. creation一个basic Store, package含countstatus, increment and decrement Mutation.
  3. creation一个component, usingStore status and Mutation.
  4. testcomponent is 否能正常modify and 显示status.

练习 2: asynchronousAction

  1. in Storein添加一个incrementAsync Action, latency1秒 after submittingincrement Mutation.
  2. in componentin添加一个按钮, 点击时分发incrementAsync Action.
  3. testasynchronousAction is 否能正常工作.

练习 3: module化Store

  1. creation两个module: countermodule and todomodule.
  2. countermodulemanagement计数status, package含increment and decrement Mutation.
  3. todomodulemanagement待办事项list, package含addTodo and toggleTodo Mutation.
  4. in componentinusing这两个module status and Mutation.
  5. testmodule化Store is 否能正常工作.