学习Vuex
Vuex的状态管理
vuex介绍
什么是状态管理?
应用程序的各种数据保存到某个位置进行管理
state:数据 view:最终模块渲染成DOM actions:修改state的行为事件
复杂的状态管理
多个组件共享状态
Vuex的状态管理
将组件的内部状态抽离出来
Vuex使用单一状态树
SSOT:Single Source of Truth,单一数据源
每个应用仅仅包含一个store实例
安装
npm install vuex@next
使用vuex4.x,需要添加next指定版本
使用
创建store(仓库)
Vuex和单纯的全局对象有什么区别呢?
Vuex的状态存储是响应式的
不能直接改变store中的状态
改变store中的状态的唯一途径就是提交(commit) mutation
这样方便我们跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态
具体步骤:
- store/index
import { createStore } from 'vuex'
// 创建一个新的store实例
const store = createStore({
state() {
return {
count: 0 // 数据
}
},
mutations: { // 方法
increment(state) {
state.counter++
},
decrement(state) {
state.counter--
}
},
})
export default store- 在全局将 store 实例作为插件安装
import store from './store/index'
createApp(App).use(store).mount('#app')
组件中使用store
state
使用store中的state数据
在模板中使用
<h2>{{$store.state.count}}</h2>
在options api 中使用
computed: {
counter() {
return this.$store.state.counter
}
}在setup中使用
setup() {
const store = useStore()
const counter = store.state.counter
}
mapState
如果需要拿state中的多个数据,在可以借助辅助函数 mapState,
setup中使用mapState
默认情况下,Vuex没有提供非常方便的的使用mapState的方式,所以我们进行了一个函数的封装
<template>
<div>
<h2>Home: {{sCounter}}</h2>
<h2>Home: {{counter}}</h2>
<h2>Home: {{name}}</h2>
<h2>Home: {{age}}</h2>
<h2>Home: {{height}}</h2>
</div>
</template>
<script>
import { mapState, useStore } from "vuex"
import { computed } from 'vue'
export default {
// options api使用mapState
computed: {
// fullName: function() { return xxx},
...mapState(["counter", "name"])
},
setup() {
const store = useStore()
const sCounter = computed(() => store.state.counter)
// 实际上放入mapState这里拿到的是这样形式的一个个函数
// {counter: function(), name: function() ...}
const storeStateFns = mapState(["counter", "name", "age", "height"])
// 封装一个函数转化一下,主要思想是把这一个个的函数放到computed里面
// 因为computed就是传入一个函数,然后会给我们返回一个ref
// {counter: ref, age: ref, ...}
const storeState = {}
Object.keys(storeStateFns).forEach(fnKey => {
// 因为内部的computed取数据的时候是通过this.$store...
// 但我们这里的fn没有this, undefined.$store 是错的
// 用bind给每个函数绑定this为一个对象,里面需要有$store这个属性
// {$store: store}
const fn = storeStateFns[fnKey].bind({ $store: store})
// 然后把函数一个方法computed, 以键值对的方式存储起来
storeState[fnKey] = computed(fn)
})
return {
sCounter,
// 最终在这里用展开运算符展开
...storeState
}
}
}
</script>把函数的封装抽离到 hooks/useState.js
import { computed } from 'vue'
import { mapState, useStore } from 'vuex'
export function useState(mapper) {
const store = useStore()
// 获取到对应的对象的functions: {name: function, age: function}
const storeStateFns = mapState(mapper)
// 对数据进行转换
const storeState = {}
Object.keys(storeStateFns).forEach(fnKey => {
const fn = storeStateFns[fnKey].bind({ $store: store })
storeState[fnKey] = computed(fn)
})
return storeState
}我们在组件使用就会简便很多
import { useState } from '../hooks/useState'
export default {
setup() {
const storeState = useState(["counter", "name", "age", "height"])
// 当然也可以是对象形式(想要重命名的时候使用)
const storeState2 = useState({
sCounter: state => state.counter,
sName: state => state.name
})
return {
...storeState,
...storeState2
}
}
}
getters
getters 的基本使用
某些属性可能需要经过变化后才使用,(就像store中的计算属性)
getters 第二个参数
getters可以接收第2个参数getters,使用getters本身的属性
getters: {
totalPrice(state, getters) {
return state.books.reduce((pre, cur) => {
return pre + cur.count * cur.price
}, 0) + " " + getters.myName
},
myName(state) {
return state.name
}
}getters 的返回函数
getters中的函数本身,可以返回一个函数,那么在使用的地方相当于可以调用这个函数
totalPrice(state) {
return (price) => {
let totalPrice = 0
for (const book of state.books) {
if (book.price < price) continue
totalPrice += book.count * book.price
}
return totalPrice
}
},
mapGetters
与mapState类似
在setup中使用mapGetters
封装好的 /useGetters
import { computed } from "vue";
import { useStore, mapGetters } from "vuex";
export function useGetters(mapper) {
const store = useStore()
const stateFns = mapGetters(mapper)
const state = {}
Object.keys(stateFns).forEach(fnKey => {
state[fnKey] = computed(stateFns[fnKey].bind({ $store: store }))
})
return state
}使用:
setup() {
const storeGetters = useGetters(["nameInfo", "ageInfo", "heightInfo"])
return {
...storeGetters
}
}
封装useMapper
我们发现前面 useState,useGetters的逻辑大部分相同,所以我们可以封装一个新的函数,根据使用的时候的 mapState还是mapGetters 来调用函数
import { computed } from 'vue' |
Mutations
更改 Vuex 的store中的状态的唯一方法是提交mutation
mutations 基本使用
在store中定义方法
mutations: {
increment(state) {
state.counter++
},
decrement(state) {
state.counter--
}
}在setup中commit事件,提交的是mutations中的方法
import { useStore } from "vuex"
setup() {
const store = useStore()
store.commit("increment", xxx) // 可以传参数
}mutation携带数据
很多时候提交mutation会携带一些数据,在mutation中第二个参数可以接收
mutations: {
addNumber(state, payload) {
state.counter += payload
}
}payload也可以是对象类型
提交的时候:可以用type,指定提交的方法名
$store.commit({
type: "addNumber",
count: 100
})mutations: {
addNumber(state, payload) {
state.counter += payload.count
}
}mutation 常量类型
主要是预防粗心的时候,commit的方法名字和mutation定义的方法名不一致
定义常量
// mutation-types.js
// 定义常量
export const INCREMENT_N = "INCREMENT_N"store中使用
import { INCREMENT_N } from '../store/mutation-types'
const store = createStore({
state() {...},
mutations: {
[INCREMENT_N](state, payload) {
state.counter += payload
}
}组件中提交事件的时候使用
import { INCREMENT_N } from '../store/mutation-types'
export default {
setup() {
...
store.commit(INCREMENT_N, 10)
}
}
mutation重要原则
mutation 必须是同步函数
- 这是因为devtool工具会记录mutation的日记
- 每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照
- 但是在mutation中执行异步操作,就无法追踪到数据的变化
- 所以Vuex的重要原则中要求 mutation必须是同步函数;
mapMutations
我们也可以借助于辅助函数,帮助我们快速映射到对应的方法中
const mutations = mapMutations(["increment", "decrement"]) |
actions
action提交mutation
actions 类似于mutations,但是action提交的是mutation,而不是直接变更状态
actions 可以包含任意异步操作
actions有一个很重要的参数 context,里面有很多属性,我们用的时候除了context.xxx使用,也可以解构出来使用
从context获取commit方法来提交一个mutation
actions的分发操作(触发actions中的方法)
在组件中使用store上的dispatch进行分发,并且可以传递参数
// store.js
mutations: {
increment(state) {
state.counter++
}
},
actions: {
// 1. 可以接收参数
incrementAction(context, payload) {
console.log(payload) // payload为接收到的参数
// 模拟异步:1s之后再提交事件
setTimeout(() => {
context.commit("increment") // 提交mutation
})
},
// 2. context 的属性
decrementAction({ commit, dispatch, state, rootState, getters, rootGetters }) {
commit("decrement")
}
}// 组件的setup中
const increment = () => {
// 分发actions,并携带参数
store.dispatch("incrementAction", {count: 100})
}另外,也可以以对象的形式进行分发
// 组件的setup中
const increment = () => {
store.dispatch({
type: "incrementAction",
count: 100
})
}
mapActions
actions 也有对应的辅助函数 mapActions
// 这样用不行.. |
actions的异步操作
actions很多时候是异步的,那么当我们组件派发actions的时候,我们也想收到结果,是请求成功了还是失败了,这时候我们可以在actions对应方法中返回 Promise,并对成功失败做处理
actions: { |
onMounted(() => { |
module
module的基本使用
什么是module?
- 由于使用单一状态树,应用的所有状态都集中到一个比较大的对象,当应用变得复杂时,store对象就变得相当臃肿,不利于管理
- 所以Vuex允许我们将store分模块
- 每个模块拥有自己的state,mutation,action,getter,甚至是嵌套子模块
使用:
- 模块内部的mutation和getter的第一个参数state是模块的局部状态对象
module的命名空间
默认情况下,模块内部的action,mutation仍然是注册在全局的命名空间中的
如果希望模块具有更高的封装度和复用性,可以在模块中添加namespaced: true
使模块更独立
之后,当模块被注册后它的所有getter、action及mutation都会自动根据模块注册的路径调整命名
module修改或派发根组件
{root: true}
actions: { |
module的辅助函数
写法一:通过完整的模块空间名来查找(不是很推荐使用)
computed: { |
写法二:第一个参数写模块名,第二个参数写属性
computed: { |
写法三:createNamespacedHelpers
辅助函数
创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数
import { createNamespacedHelpers } from 'vuex' |
setup中使用
修改之前的 hooks,useState,useGetters,考虑模块的情况
import { mapState, createNamespacedHelpers} from 'vuex' |
// useGetters同理
import { mapGetters, createNamespacedHelpers } from "vuex"; |
使用:
setup() { |