vuex学习
state
定义一个store
const store = createStore({ //相当于data,不过这个是全局的,所有组件都能访问 state(){ return{ count: 0 } }, //所有的方法定义在这里 mutations: { increment(state) { state.count++ } } })
使用state的值
<template> <p>??????????????????????????</p> <!-- {{this.$store.state.count}} 我们不会这么写,而是搞一个计算属性,如下--> {{count}} <button @click="update">点我</button> </template> <script> export default { name: "MyTest", methods: { update: function (){ this.$store.commit('increment'); } }, computed:{ //要是想用state里面的值,就搞一个计算属性,然后再引用这个计算属性 count(){ return this.$store.state.count } } } </script> <style scoped> </style>
辅助函数 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([ // 映射 this.count 为 store.state.count 'count', 'num' //也就是说state里面有这两个值,且名字一样,才能这样用 ])
这个时候有一个问题,使用上面的方法时,占用了计算属性的位置,那当我们想定义自己的计算属性时该咋办呢。
这个时候,就要将多个对象合为一个,也就是将我们新定义的计算属性和mapState合二为一
computed: { localComputed () { /* ... */ }, //我们新定义的计算属性 // 使用对象展开运算符将此对象混入到外部对象中 ...mapState({ // ... }) }
Getter
有时候我们需要从 store 中的 state 中派生出一些状态,也就是对state的数据进行处理,然后获得处理结果,例如对列表进行过滤并计数:
computed: { doneTodosCount () { return this.$store.state.todos.filter(todo => todo.done).length } }
如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。
const store = createStore({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: true }, { id: 3, text: '...', done: false } ] }, getters: { doneTodos (state) { return state.todos.filter(todo => todo.done) } } })
获得doneTodos:
{{$store.getters.doneTodos}}
结果:
当然,最好还是在组件里将它定义为计算属性
computed: { doneTodosCount () { return this.$store.getters.doneTodosCount } }
还可以将其定义为一个方法,来实现对其传参,这很好用
getters: { // ... getTodoById: (state) => (id) => { return state.todos.find(todo => todo.id === id) } }
调用:
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: true }
当然,类似于mapState,可以使用mapGetters辅助函数
import { mapGetters } from 'vuex' //引入 export default { // ... computed: { // 使用对象展开运算符将 getter 混入 computed 对象中 ...mapGetters([ 'doneTodosCount', //同名 'anotherGetter', // ... ]) } }
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。mutation可以接收state作为参数,从而修改state的值
const store = createStore({ state: { count: 1 }, mutations: { increment (state) { // 变更状态 state.count++ } } })
不能直接调用mutation 处理函数,而是要调用store.commit 方法
store.commit('increment')
可以向 store.commit
传入额外的参数
// ... mutations: { increment (state, n) { state.count += n } } store.commit('increment', 10)
参数的类型最好是一个对象,这样读代码很容易
// ... mutations: { increment (state, payload) { state.count += payload.amount } } store.commit('increment', { amount: 10 })
对象风格的提交方式
提交 mutation 的另一种方式是直接使用包含 type
属性的对象:
store.commit({ type: 'increment', amount: 10 })
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此处理函数保持不变:
mutations: { increment (state, payload) { state.count += payload.amount } }
使用常量代替mutation的事件,当数量多的时候使用
这样也不容易写错
// mutation-types.js 常量文件 export const SOME_MUTATION = 'SOME_MUTATION' // store.js import { createStore } from 'vuex' import { SOME_MUTATION } from './mutation-types' const store = createStore({ state: { ... }, mutations: { // 我们可以使用 ES2015 风格的计算属性命名功能 // 来使用一个常量作为函数名 [SOME_MUTATION] (state) { // 修改 state } } })
注意事项:Mutation 必须是同步函数 ,不能写异步函数
类似于mapState、mapGetters,Mutation有mapMutations
mapMutations辅助函数将组件中的 methods 映射为 store.commit
调用(需要在根节点注入 store
)
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')` }) } }
Action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
也就是说mutation操作state,Action操作mutation
const store = createStore({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } } })
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit
提交一个 mutation,或者通过 context.state
和 context.getters
来获取 state 和 getters。
Action 通过 store.dispatch
方法触发:
store.dispatch('increment')
可以在 action 内部执行异步操作:
actions: { incrementAsync ({ commit }) { setTimeout(() => { commit('increment') }, 1000) } }
Actions 支持同样的载荷方式和对象方式进行分发:
// 以载荷形式分发 store.dispatch('incrementAsync', { amount: 10 }) // 以对象形式分发 store.dispatch({ type: 'incrementAsync', amount: 10 })
在组件中使用 this.$store.dispatch('xxx')
分发 action,或者使用 mapActions
辅助函数将组件的 methods 映射为 store.dispatch
调用(需要先在根节点注入 store
)
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')` }) } }
Action 通常是异步的,那么如何知道 action 什么时候结束呢?
需要明白 store.dispatch
可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch
仍旧返回 Promise:
actions: { actionA ({ commit }) { return new Promise((resolve, reject) => { setTimeout(() => { commit('someMutation') resolve() }, 1000) }) } }
现在你可以:
store.dispatch('actionA').then(() => { // ... })
在另外一个 action 中也可以:
actions: { // ... actionB ({ dispatch, commit }) { return dispatch('actionA').then(() => { commit('someOtherMutation') }) } }
最后,如果我们利用 async / await,我们可以如下组合 action:
// 假设 getData() 和 getOtherData() 返回的是 Promise actions: { async actionA ({ commit }) { commit('gotData', await getData()) }, async actionB ({ dispatch, commit }) { await dispatch('actionA') // 等待 actionA 完成 commit('gotOtherData', await getOtherData()) } }
一个
store.dispatch
在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。
Module
当项目变大时,store对象可能变得臃肿,为了解决这个问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... } } const store = createStore({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
const moduleA = { state: () => ({ count: 0 }), mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } } }
项目结构
├── index.html ├── main.js ├── api │ └── ... # 抽取出API请求 ├── components │ ├── App.vue │ └── ... └── store ├── index.js # 我们组装模块并导出 store 的地方 ├── actions.js # 根级别的 action ├── mutations.js # 根级别的 mutation └── modules ├── cart.js # 购物车模块 └── products.js # 产品模块
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端