Vuex 笔记 📒

参考来源:

  1. 官方文档

Vex 是什么?

vuex 是一个专为 vue 应用程序开发的 状态管理模式。保证状态以一种可预测的方式发生变化。

Vuex 作用一览:

vuex作用图.png

简单的例子🌰:

// Vue.use(Vuex)
const Store = new Vuex.Store({
    state: {
        count: 0,
    },
    mutations: {
        increment (state) {
            state.count ++
        }
    }
})
// 使用
store.commit('increment');
console.log(store.state.count); // -> 1

约定通过提交 mutation 的方式,而非直接改变 store.state.count,更加明确追踪到状态的变化。

一个 Vuex 的 Store 分为四个部分:

  1. State
  2. Getter
  3. Mutation
  4. Action

State(单一状态树)

在vue中使用 State 状态的最简单的方法就是在计算属性(computed)中返回某个状态值:

const Counter = {
    template: `<div>{{ count }}</div>`,
    computed: {
        count() {
            return this.$store.state.count
        }
    }
}

上述过程中,每当 store 中的 count 状态值发生变化,computed 属性都能监听得到,并且触发相关联的 DOM的更新。

mapState 辅助函数

当一个组件需要获取多个状态值,将这些状态都声明在计算属性中会显得臃肿不堪,Vuex 提供了一个 mapState 函数简化这一操作:

import { mapState } from 'vuex';
export default {
    //...
    computed: {
        otherComputed() {},
        // ...
        ...mapState({
            count: state => state.count,
            // 传字符串参数 'count' 等同于 `state => state.count`
            countAlias: 'count',
            // 为了能够使用 `this` 获取局部状态,必须使用常规函数
            countPlusLocalState(state) {
                return state.count + this.localCount
            }
        })
    }
}

Getter

有时候我们需要从 Store 中的 State 派生出一些状态,例如对列表进行过滤并记数:

computed: {
    doneTodosCount() {
        return this.$store.store.todos.filter(todo => todo.done).length
    }
}

如果有多个组件需要用这个属性,那么就需要在每个组件中都写这么长的表达式,难免有些尴尬😅。

这时候 Getter 就派上了用处(可以认为是 Store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,依赖变了,返回值也随之改变。

const store = new Vuex.store({
    state: {
        todos: [
            { id: 1, text: '...', done: true},
            { id: 1, text: '...', done: false},
        ],
    },
    getters: {
        doneTodos: state => state.todo.filter(todo => todo.done)
    }
})

在组件中通过使用 store.getters 对象,可以以属性的形式方为这些值:

store.getters.doneTodos
// -> [{ id: 1, text: '...', done: true }]

Getter也可以接受其他 getter 作为第二个参数:

getters: {
    // ...
    doneTodosCount: (state, getters) => getters.doneTodos.length,
}
// 使用
store.getters.doneTodosCount // -> 1

通过方法访问

可以对 getter 的返回值做些手脚:

getters: {
    // ...
    getTodosById: (state) => (id) => {
        return state.todos.find(todo => todo.id === id)
    }
}
// 使用
store.getters.getTodoById(2)
// -> { id: 2, text: '...', done: false }

使 getter 返回一个函数时,可以适应许多常用场景。getter 在使用 方法访问时,每次都会进行调用而不会缓存结果。

mapGetters 辅助函数

类似于 mapState ,vuex 也提供了 mapGetters 方便使用:

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

Mutation

更改 Vuex 的 store 的状态的唯一方法就是提交 mutation。每个 mutation 都有字符串的 事件类型(type) 和一个 回调函数(handler)

const store = new Vuex.store({
    state: {
        count: 1,
    },
    mutation: {
        icrement (state) {
            state.count ++ 
        },
        decrement (state, { delta }) {
            state.count -= delta
        } 
    }
});

// 使用
store.commit('icrement');
store.commit('decrement', { delta: 10 });
// 对象风格的提交方式
store.commit({
    type: 'decrement',
    delta: 10,
})

Mutation 需遵守 Vue 的响应规则:

  1. 提前初始化 store 中所有的属性。

  2. 当需要在对象上添加新属性时

    • 使用 Vue.set(obj, 'newProp', 123) 或者

    • 以新对象替换老对象,例如:

      state.obj = { ...state.obj, newProp: 123 }
      

使用常量替代 Mutation 事件类型

使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然

// mutation-types.js
export const SOME_MUTATION = 'SOME_' 

使用类型常量:

// store.js
import Vuex from 'vuex';
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.store({
    state: { /* ... */ },
    mutations: {
        // 使用 ES6 风格的常量作为函数名。
        [SOME_MUTATION] (state) {
            // mutate state
        }
    }
})

Mutation 必须是同步函数 ⚠️

任何回调函数中进行的状态的改变都是不可追踪的!在 mutation 中混合异步调用会导致你的程序很难调试。

在组件中提交 Mutation

  1. 在组件中使用 this.$store.commit('xxx') 提交 mutation;

  2. 使用 mapMutation 辅助函数将 mutation 方法混入组件的 methods 中:

    import { mapMutatioins } from 'Vuex';
    export default {
        // other props
        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,不同于:

  1. Action 提交的是 mutation,而不是直接变更状态;
  2. Action 可以包含任意异步操作;
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    },
    // 参数结构:
    decrement ({ commit }) {
      commit('decrement')
    }
  }
});

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此可以调用 context.commit 方法提交一个 Mutation,或者使用 context.statecontext.getters 获取 state 和 getters。

⚠️context 对象并不是 store 实例本身

分发 Action

store.dispatch('increment');

与 Mutation 不一样的是,在 Action 内部是可以执行 异步 操作:

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

在组件中分发 Action

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

store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  },
  // ...
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}
// 使用:
store.dispatch('actionB').then(() => {
  // ...
})

以及比较复杂的使用:

// 假设 getData() 和 getOtherData() 返回的是 Promise

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}

规则建议:

  1. 应用层级的状态应该集中到单个 store 对象中。
  2. 提交 mutation 是更改状态的唯一方法,这个过程是同步的。
  3. 异步逻辑都应该封装到 action 中。

相关面试题

📒 什么时候用 Vuex

  1. 多个组件依赖于同一状态。
  2. 来自不同组件行为需要更改同一状态。

📒 Vuex 中状态是对象时,使用时要注意什么?

对象时引用类型,复制后改变属性还是会影响原始数据,所以在赋值前,先试用DeepClone

📒 怎么在组件中批量使用Vuex的state状态?

使用mapState辅助函数, 利用对象展开运算符将state混入computed对象中

import {mapState} from 'vuex'
export default{
    computed:{
        ...mapState(['price','number'])
    }
}

📒 Vuex中要从state派生一些状态出来,且多个组件使用它,该怎么做?

使用 getter 属性:

const store = new Vuex.store({
    state: {
        price: 10,
        number: 10,
        discount: 0.7,
    },
    getters: {
        total: state => state.price * state.number,
        discountTotal: (state, getters) => state.discount * getters.total,
    },
})

面试问题待收录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349