VUE Study Day14 - vuex

2021/2/27 vue学习提升

# state

# 定义

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

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 使用

store.commit('increment') //触发mutations的increment更新

console.log(store.state.count) // -> 1 ,调用
1
2
3

# 全局调用

Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)):

const app = new Vue({
  el: '#app',
  // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})
1
2
3
4
5
6
7
8
9
10
11

通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。让我们更新下 Counter 的实现:

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}
1
2
3
4
5
6
7
8

# mapState(常用)

mapState作用:同时将多个状态声明为计算属性

// 当计算属性名称与state子节点名称相同时,可以直接传字符串数组
  computed: {
    ...mapState("d2admin/user", [
      "info"
    ]),
  },
1
2
3
4
5
6
  • 当state有多个modules时,可以按需导入

    
    computed: {
        ...mapState("d2admin/user", [
          "info"
        ]),
      },
      
      
    比如2层结构:
    -------------------------------
    import d2admin from './modules/d2admin'
    //首先向store暴露d2admin
    export default new Vuex.Store({
        modules: {
            d2admin,
         }
    })
    
    cat ./modules/d2admin/modules/user.js
    // 其次d2admin的子modules包含user.js,里面包含info属性
    export default {
      namespaced: true,
      state: {
        // 用户信息
        info: {}
      },
    }
    // 所以层级为"d2admin/user",最后得到一个同名的this.info(映射为)
    
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

# getters

通俗讲就是对state的加工,如同计算属性,举个例子:

computed: {
  doneTodosCount () {
    return this.$store.state.todos.filter(todo => todo.done).length
  }
}
1
2
3
4
5

如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。

# getters语法

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13

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

getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}
1
2
3
4
5
6

# 访问getters

属性访问

此时有缓存

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

// 在计算属性中调用
computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
}
1
2
3
4
5
6

通过方法访问

此时无缓存

你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。

getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
1
2
3
4
5
6
7

# mapGetters 辅助函数

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

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

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

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

# Mutations

mutations必须是同步函数,否则将无法追踪。

state不能直接修改,需要定义相应的mutations来调用。如:

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})

// 调用
store.commit('increment')
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# payload

调用时可以传参,如:

mutations: {
  increment (state, n) {
    state.count += n
  }
}
store.commit('increment', 10)
1
2
3
4
5
6

以对象形式传参

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
store.commit('increment', {
  amount: 10
})
1
2
3
4
5
6
7
8

# 以对象形式调用mutations

提交 mutation 的另一种方式是直接使用包含 type 属性的对象:

store.commit({
  type: 'increment',
  amount: 10
})
1
2
3
4

当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
1
2
3
4
5

# 响应式更新

注意事项:

  1. 最好提前在你的 store 中初始化好所有所需属性。

  2. 当需要在对象上添加新属性时,你应该

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

# mapMutations

你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 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')`
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Actions

Action 类似于 mutation,不同在于:

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

demo:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 state 和 getters。当我们在之后介绍到 Modules (opens new window) 时,你就知道 context 对象为什么不是 store 实例本身了。

实践中,我们会经常用到 ES2015 的 参数解构 (opens new window) (opens new window)来简化代码(特别是我们需要调用 commit 很多次的时候):

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}
1
2
3
4
5

# 触发Action

# 分类Action

Action 通过 store.dispatch 方法触发:

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

// 以对象形式分发
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})
1
2
3
4
5
6
7
8
9
10

# 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')`
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 组合action

如果我们利用 async / await (opens new window) (opens new window),我们可以如下组合 action:

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

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}
1
2
3
4
5
6
7
8
9
10
11
Last Updated: 2023/3/12
只爱西经
林一