一、Vuex基本使用
1.1 安装搭建
-
安装vuex(本文使用4.x)
npm install vuex
-
创建store
import { createStore } from 'vuex' const store = createStore({ state() { return { counter: 0 } } }) export default store -
app.use(store)
import { createApp } from 'vue' import store from './store' import App from './App.vue' createApp(App).use(store).mount('#app') -
tempate -> $store.state.counter
<template> <div class="app"> <h2>App当前计数: {{ $store.state.counter }}</h2> </div> </template>
1.2 Vuex的状态管理
-
每一个Vuex应用的核心就是store(仓库):
- store本质上是一个容器,它包含着你的应用中大部分的状态(state);
-
Vuex和单纯的全局对象有什么区别呢?
-
第一:Vuex的状态存储是响应式的
- 当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新
-
第二:不能直接改变store中的状态
-
改变store中的状态的唯一途径就显示提交 (commit) mutation
-
这样使得我们可以方便的跟踪每一个状态的变化,从而能够通过一些工具帮助更好的管理应用的状态
-
-
1.3 组件中使用store
<template>
<div class="home">
<h2>Home当前计数: {{ $store.state.counter }}</h2>
<h2>Computed当前计数: {{ storeCounter }}</h2>
<h2>Setup当前计数: {{ counter }}</h2>
<button @click="increment">+1</button>
</div>
</template>
<script>
export default {
computed: {
storeCounter() {
return this.$store.state.counter
}
}
}
</script>
<script setup>
import { toRefs } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
const { counter } = toRefs(store.state)
function increment() {
// 不能直接修改
// store.state.counter++
store.commit('increment')
}
</script>
二、Vuex核心概念
2.1 State
2.1.1. 基本使用
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
name: 'coder',
level: 88,
avatarUrl: 'http://123.431.342.5:xxx.png'
}
}
})
export default store
<template>
<div class="home">
<!-- 在模板中直接使用多个状态 -->
<h2>name: {{ $store.state.name }}</h2>
<h2>level: {{ $store.state.level }}</h2>
<h2>avatarUrl: {{ $store.state.avatarUrl }}</h2>
</div>
</template>
2.1.2 映射使用(mapState)
- options api
<template>
<div class="home">
<!-- 计算属性(映射状态:数组语法) -->
<h2>name: {{ name }}</h2>
<h2>level: {{ level }}</h2>
<!-- 计算属性(映射状态:对象语法) -->
<h2>name: {{ sName }}</h2>
<h2>level: {{ sLevel }}</h2>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['name', 'level', 'avatarUrl']),
...mapState({
sName: state => state.name,
sLevel: state => state.level
})
}
}
</script>
-
composition api
- 通过 bind 绑定
this
import { computed } from 'vue' import { useStore, mapState } from 'vuex' const { name, level } = mapState(['name', 'level']) const store = useStore() const cName = computed(name.bind({ $store: store })) const cLevel = computed(level.bind({ $store: store }))- 封装hook
import { computed } from 'vue' import { useStore, mapState } from 'vuex' export function useState(mapper) { const store = useStore() const stateFnsObj = mapState(mapper) const state = {} Object.keys(stateFnsObj).forEach(fnKey => { state[fnKey] = computed(stateFnsObj[fnKey].bind({ $store: store })) }) return state }import { useState } from '../hooks/useState' const { name, level } = useState(['name', 'level'])- 使用
toRefs直接对store进行解构(推荐)
import { toRefs } from 'vue' import { useStore } from 'vuex' const store = useStore() const { name: cName, level: cLevel } = toRefs(store.state) - 通过 bind 绑定
2.2 getter
2.2.1 基本使用
-
直接使用
-
引入别的getters
-
返回函数, 接收参数
import { createStore } from 'vuex' const store = createStore({ state() { return { counter: 100, name: 'coder', level: 88, avatarUrl: 'http://123.431.342.5:xxx.png', friends: [ { id: 111, name: "kobe", age: 24 }, { id: 112, name: "james", age: 22 }, { id: 113, name: "curry", age: 21 } ], } }, getters: { doubleCounter(state) { return state.counter * 2 }, totalAge(state) { return state.friends.reduce((pre, cur) => { return pre + cur.age }, 0) }, // 引入别的getters info(state, getters) { return `name: ${state.name} level: ${state.level} friendsTotalAge: ${getters.totalAge}` }, // 引入函数,接收参数 getFriendById(state) { return function(id) { return state.friends.find(friend => friend.id === id) } } } }) export default store
2.2.2 映射使用
-
options api
import { mapGetters } from 'vuex' export default { computed: { ...mapGetters(['doubleCounter', 'totalAge']), ...mapGetters(['getFriendById']) } } -
composition api
import { useStore, mapGetters } from 'vuex' // const { info: infoFn } = mapGetters(["info"]) // const sInfo = computed(infoFn.bind({ $store: store })) const store = useStore() const sInfo = computed(() => store.getters.info) const getFriendById = computed(() => store.getters.getFriendById)
2.3 Mutations
2.3.1 重要原则
- 修改state, 必须使用mutation
- 不要在mutation执行异步操作, 必须同步操作
2.3.2 基本使用
-
直接使用
-
传入参数
const store = createStore({ // state() {...}, mutations: { increment(state) { state.counter++ }, changeName(state, payload) { state.name = payload }, incrementLevel(state) { state.level++ }, changeInfo(state, newInfo) { state.name = newInfo.name state.level = newInfo.level } } })import { useStore } from 'vuex' const store = useStore() function incrementLevel() { store.commit('incrementLevel') }
2.3.3 映射使用
-
options api
<template> <div class="home"> <button @click="changeName('张三')">修改name</button> <button @click="incrementLevel">数组语法修改level</button> <button @click="storeIncrementLevel">对象语法修改level</button> <button @click="changeInfo({name: '李四', level: 120})">修改Info</button> <h2>store name: {{ $store.state.name }}</h2> <h2>store level: {{ $store.state.level }}</h2> </div> </template> <script> import { CHANGE_INFO } from '../store/mutations_types' import { mapMutations } from 'vuex' export default { methods: { ...mapMutations(['changeName', 'incrementLevel', CHANGE_INFO]) // 对象语法重命名 ...mapMutations({ storeIncrementLevel: 'incrementLevel' }) } } </script> -
composition api
import { CHANGE_INFO } from '../store/mutations_types' import { useStore, mapMutations } from 'vuex' const store = useStore() const mutationsObj = mapMutations(['changeName', 'incrementLevel', CHANGE_INFO]) const newMutations = {} Object.keys(mutationsObj).forEach(key => { newMutations[key] = mutationsObj[key].bind({ $store: store }) }) const { changeName, incrementLevel, changeInfo } = newMutations
2.3.4 使用常量
- 定义常量:mutation-type.js
export const CHANGE_INFO = 'changeInfo'
- 定义mutation
[CHANGE_INFO](state, newInfo) { state.name = newInfo.name state.level = newInfo.level } - 提交mutation
this.$store.commit(CHANGE_INFO, { name: '李海', level: 99 })
import { useStore } from 'vuex' const store = useStore() function changeInfo() { store.commit(CHANGE_INFO, { name: '李海', level: 99 }) }
2.4 Actions
2.4.1 actions的基本使用
-
定义action
const store = createStore({ state() { return { counter: 100, name: 'coder' }, mutations: {...}, actions: { incrementAction(context) { // console.log(context.commit) // 用于提交mutation // console.log(context.getters) // getters // console.log(context.state) // state context.commit("increment") }, changeNameAction(context, payload) { context.commit('changeName', payload) } } }) -
dispatch派发action
<script> export default { methods: { incrementAction() { this.$store.dispatch('incrementAction') }, // changeNameAction() { // this.$store.dispatch('changeNameAction', '赵四') // } } } </script> <script setup> import { useStore } from 'vuex' const store = useStore() function changeNameAction() { store.dispatch('changeNameAction', '王云') } </script>
2.4.2 actions的辅助函数
<script>
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions(['incrementAction'])
}
}
</script>
<script setup>
import { useStore, mapActions } from 'vuex'
const store = useStore()
// 使用mapActions
const actionsObj = mapActions(['changeNameAction'])
const newActions = {}
Object.keys(actionsObj).forEach(key => {
newActions[key] = actionsObj[key].bind({ $store: store })
})
const { changeNameAction } = newActions
</script>
2.4.3 actions的异步操作
-
store
const store = createStore({ state() { return { counter: 100, name: 'coder', // 服务器数据 banner: [], recommend: [] } }, mutations: { changeBanner(state, banner) { state.banner = banner }, changeRecommend(state, recommend) { state.recommend = recommend } }, actions: { async fetchHomeMultidataAction(context) { // 1. 返回Promise,给Promise设置then // fetch('http://xxx.207.xxx.32:8000/home/multidata').then(res => { // res.json().then(data => { // console.log(data) // }) // }) // 2. Promise链式调用 // fetch('http://xxx.207.xxx.32:8000/home/multidata').then(res => { // return res.json() // }).then(data => { // console.log(data) // }) // 3. async/await const res = await fetch('http://xxx.207.xxx.32:8000/home/multidata') const data = await res.json() // console.log(data) context.commit('changeBanner', data.data.banner.list) context.commit('changeRecommend', data.data.recommend.list) } } }) -
使用
<template> <div class="home"> <h2>Home Page</h2> <ul> <template v-for="item in $store.state.banner" :key="item.acm"> <li>{{ item.title }}</li> </template> </ul> </div> </template> <script setup> import { useStore } from 'vuex' const store = useStore() // 告诉vuex发送网络请求 store.dispatch('fetchHomeMultidataAction') </script>
2.4.4 action结果返回Promise
- Action 通常是异步的,那么如何知道 action 什么时候结束呢?
- 可以通过让action返回Promise,在Promise的then中来处理完成后的操作
2.5 Module
2.5.1 module的基本使用
- 抽取到对象:
- state
- mutations
- getters
- actions
export default {
state: () => ({
// 服务器数据
banner: [],
recommend: []
}),
mutations: {
changeBanner(state, banner) {
state.banner = banner
},
changeRecommend(state, recommend) {
state.recommends = recommend
}
},
actions: {
fetchHomeMultidataAction(context) {
return new Promise(async (resolve, reject) => {
const res = await fetch("http://xxx.207.xxx.32:8000/home/multidata")
const data = await res.json()
// 修改state数据
context.commit("changeBanner", data.data.banner.list)
context.commit("changeRecommend", data.data.recommend.list)
resolve("aaaaa")
})
}
}
}
- modules: { home: 对象 }
- state.home.xxx
- getters.xxx
- commit
- dispatch
<template>
<div class="home">
<h2>Home Page</h2>
<ul>
<template v-for="item in $store.state.home.banner" :key="item.acm">
<li>{{ item.title }}</li>
</template>
</ul>
<hr>
<h2>Counter Page</h2>
<!-- 使用state时,需要state.moduleName.xxx -->
<h3>counter: {{ $store.state.counter.count }}</h3>
<!-- 使用getter时,直接getters.xxx -->
<h3>doubleCounter: {{ $store.getters.doubleCount }}</h3>
<button @click="decrement">count模块-1</button>
</div>
</template>
<script setup>
import { useStore } from 'vuex'
const store = useStore()
// 告诉vuex发送网络请求
// 派发事件和提交mutation时,默认不需要跟模块
store.dispatch('fetchHomeMultidataAction')
function decrement() {
store.dispatch('decrementAction')
}
</script>
2.5.2 module的命名空间
-
默认情况下,模块内部的action和mutation仍然是注册在全局的命名空间中的:
-
这样使得多个模块能够对同一个 action 或 mutation 作出响应
-
Getter 同样也默认注册在全局命名空间
-
-
如果希望模块具有更高的封装度和复用性,可以添加
namespaced: true的方式使其成为带命名空间的模块:- 当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名
- namespaced: true
- getters["home/xxx"]
- commit("home/xxx")
<template>
<div class="home">
<h2>Home Page</h2>
<!-- 使用state时,需要state.moduleName.xxx -->
<h2>counter: {{ $store.state.counter.count }}</h2>
<!-- 使用getter时,直接getters.xxx -->
<h2>doubleCounter: {{ $store.getters['counter/doubleCount'] }}</h2>
<button @click="decrement">count模块-1</button>
</div>
</template>
<script setup>
import { useStore } from 'vuex'
const store = useStore()
function decrement() {
store.dispatch('counter/decrementAction')
}
</script>
- 在命名空间模块中修改根组件
changeRootNameAction({commit, dispatch, state, rootState, getters, rootGetters}) {
// console.log(state)
// commit('decrement')
// 第二个是额外传入的参数
commit('changeName', '柒柒', { root: true })
dispatch('changeNameAction', null, { root: true })
}