Vue3之Vuex
1. Vuex的基本使用
vuex的安装:
npm i vuex
vuex的配置,@/store/index.js:
import {createStore} from 'vuex'//导入createStore构造函数
export default createStore({
state:{ //Vuex的状态,实际上就是存数据的地方
person:{
name:'jack',
age:200
}
},
getters:{ //提供获取Vux状态的方式, 注意在组件中调用时getPerson是以属性的方式被访问
getPerson(state){
return state.person
}
},
mutations:{ //提供直接操作Vuex的方法,注意mutations里的方法中不能有任何异步操做
ageGrow(state, value){
//第一个参数state为Vuex状态;第二个参数为commit函数传来的值
state.person.age += value
}
},
actions:{ //提供通过mutations方法来简介操作Vuex的方法
ageGrow(context, value){
//第一个参数context为上下文,提供一些方法;第二个参数为dispatch函数传来的值
context.commit('ageGrow', value)
}
},
})
在@/main.js中引入:
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
const app = createApp(App)
app.use(store)
app.mount('#app')
在组件中使用Vuex:
<template>
<h1>名字:{{person.name}}--年龄:{{person.age}}</h1>
<input type="text" v-model="value">
<button @click="ageGrow(value)">增加年龄</button>
<!-- 在input框输入一个数字,点击按钮,可以看到年龄发生变化,说明Vuex正常工作 -->
</template>
<script>
import {useStore} from 'vuex'
import {ref} from 'vue'
export default {
setup(){
const store = useStore() //获取store对象
let person = store.getters.getPerson //从组件中获取状态(数据)person 方式一
// let person = store.state.person //从组件中获取状态(数据)person 方式二
let value = ref('输入年龄的增量')
function ageGrow(ageGrowth){
ageGrowth = parseInt(ageGrowth)
if(isNaN(ageGrowth)){
ageGrowth = 0
}
store.dispatch('ageGrow', ageGrowth)
//通过dispatch来调用actions里的'ageGrow'方法,参数为ageGrowth
//actions的方法又会通过commit来调用mutations里的方法,从而引起状态(数据)的变化
//也可以在组件里跳过dispatch actions,直接store.commit
}
return {
person,
value,
ageGrow
}
}
}
</script>
<style></style>
把代码复制粘贴到脚手架中运行,可以看到直观的效果,有助于理解。
小结:安装完vuex之后,首先要用creatRouter构造函数创建一个router对象,并在main.js中引入这个对象。然后在组件中,通过userStore方法来获取这个router对象,进一步通过getter或者state可以得到Vuex状态(数据),通过dispatch->actions->mutations->state的数据传送方式可以操作和改变Vuex的状态(数据)
2. Module
Vuex官方原话:“由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。”
什么叫单一状态树呢,其实就是上文中的state对象。在Vuex的基本使用中,我们使用state对象来存储Vuex的状态,state对象里面可以嵌套其他的对象,它是一个树形的结构,而且这个state对象是唯一的,所有的状态都要存储在这一个对象里。因此,我们称之为单一状态树。
这种单一状态树的弊端是显而易见的,对于中大型项目来说,要托管给Vuex的状态有很多,把这些海量的数据如果都塞到一个文件里面的一个对象里面,未免显得过于臃肿,不管是开发起来还是维护起来都会有很多不变。
对此,官方给出了解决方案:
“为了解决以上问题,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割”
2.1 vuex中模块化的基本使用
vuex的模块化没什么难理解的,就是把store给拆开,一个对象拆成多个对象,一个文件拆成多个文件,并且彼此可以拥有独立的命名空间。
道理很简单,所以直接看样例:
文件结构
├──src
├── components
│ └── Test.vue
└── store
├── index.js
└── modules
├── male.js
└── female.js
index.js
import {createStore} from 'vuex'
import female from './modules/female' //导入模块
import male from './modules/male' //导入模块
export default createStore({
modules:{ //使用模块
female,
male
}
})
male.js
export default {
namespaced:true, //namespaced:true代表该模块带有独立命名空间
state:{ //否则,默认是处于全局命名空间,就和非模块化一样
personList:[
{name:'张飞', id:'004'},
{name:'武则天', id:'005'},
{name:'秀吉', id:'006'},
]
},
mutations:{
addMutation(state, value){ //往personList中添加一个人
state.personList.push(value)
},
removeMutaion(state, value){ //往personList中删除一个人
state.personList = state.personList.filter((el) => el.id != value.id)
}
},
actions:{
addAction(context, value){
setTimeout(() => {
context.commit('addMutation', value) // ->'male/addMutation'
}, 1000);
},
removeAction(context, value){
context.commit('removeMutaion', value)
}
},
getters:{
personList(state){
return state.personList
}
}
}
female.js
export default {
namespaced:true, //namespaced:true代表该模块带有独立命名空间
state:{ //否则,默认是处于全局命名空间,就和非模块化一样
personList:[
{name:'李白', id:'001'},
{name:'孙尚香', id:'002'},
{name:'大乔', id:'003'},
]
},
mutations:{
addMutation(state, value){ //往personList中添加一个人
state.personList.push(value)
},
removeMutaion(state, value){ //往personList中删除一个人
state.personList = state.personList.filter((el) => el.id != value.id)
}
},
actions:{
addAction(context, value){
setTimeout(() => {
context.commit('addMutation', value) // ->'female/addMutation'
}, 1000);
},
removeAction(context, value){
context.commit('removeMutaion', value)
}
},
getters:{
personList(state){
return state.personList
}
}
}
Test.vue
<template>
<h1>女人:</h1>
<li v-for="femalePerson in femalePersons" :key="femalePerson.id">
{{femalePerson.name}}
<button @click="addToMale(femalePerson)">添加到男人</button>
</li>
<h1>男人:</h1>
<li v-for="malePerson in malePersons" :key="malePerson.id">
{{malePerson.name}}
<button @click="addToFemale(malePerson)">添加到女人</button>
</li>
<!-- 有两个列表,分布是男人和女人,通过点击按钮可以把列表中的某些项添加到另一个列表中 -->
<!-- 建议粘贴复制并运行代码,这样更直观 -->
</template>
<script>
import { computed } from '@vue/runtime-core';
import {useStore} from 'vuex'
export default {
setup(){
let store = useStore()
let malePersons = computed(() => store.getters['male/personList']) //通过getter获取state
let femalePersons = computed(() => store.state.female.personList) //直接获取state
function addToMale(person){
store.dispatch('male/addAction', person)
store.dispatch('female/removeAction', person)
//如果模块中namespaced === true,那么要在方法名前面添加模块的逻辑路径
//index.js里使用的模块为路径的起点。
//比如index里面有一个moduleA,moduleA有一个子模块moduleB,module有一个action是actionx
//那么调用方式为 store.dispatch('moduleA/moduleB/actionx', value)
}
function addToFemale(person){
store.dispatch('female/addAction', person)
store.dispatch('male/removeAction', person)
}
return {
malePersons,
femalePersons,
addToMale,
addToFemale
}
}
}
</script>
<style></style>
2.2 在命名空间中访问全局内容
什么是全局内容?不在同一个模块中的内容就是全局内容。
比如,对于上文中的female模块来说,male模块中的getters state action mutations就是全局内容,接下来将会讲解如何在一个模块中访问到全局内容。
为了便于理解,我创造了一个新的样例:
├──src
├── components
│ └── Test.vue
└── store
├── index.js
└── modules
├── moduleA.js
└── moduleB.js
index是所有的模块的根,moduleA和moduleB是两个子模块,接下来要做的事情就是在index.js、moduleA.js、moduleB.js中写一些getters state action mutations,最终达成的效果是在index中访问moduleA的内容,在moduleA中访问moduleB的内容,在moduleB中访问index的内容。
index.js:
import {createStore} from 'vuex'
import moduleA from './modules/moduleA' //导入模块
import moduleB from './modules/moduleB' //导入模块
export default createStore({
state:{info:{name:'root', num:100}},
getters:{
state: state => state,
moduleAStateThroughRoot: (state, getters, rootState, rootGetters) => {
//在getter中访问全局内容
//rootState为根状态,rootGetters为根getters,这两个属性是访问全局内容的工具
//通过模块A的来获得root的状态
//因为这里本身就是模块的根,所以这里的state和rootState、getters和rootGetters是一个东西
// console.log(state === rootState, getters === rootGetters); // true true
return rootGetters['moduleA/state']
// return rootState.moduleA
}
},
actions:{addAction: (context, value) => {context.commit('moduleA/addMutation', value)}},
mutations:{addMutation: (state, value) => {state.info.num += value}},
modules:{ //使用模块
moduleA,
moduleB
}
})
moduleA.js:
export default {
namespaced :true,
state:{info:{name:'moduleA', num:200}},
getters:{
state: state => state,
moduleBStateThroughModuleA: (state, getters, rootState, rootGetters) => {
//在getter中访问全局内容
//rootState为根状态,rootGetters为根getters,这两个属性是访问全局内容的工具
//通过模块B的来获得模块A的状态
// return rootState.moduleB
return rootGetters['moduleB/state']
}
},
actions:{
addAction: (context, value) => {context.commit('moduleB/addMutation', value, {root:true})},
//context中包含dispatch, commit, getters, rootGetters
//在action中调用全局mutation,只需要在commit函数中添加第三个参数{root:true},这样第一个参数就会被当作全局内容来解析。
//在action中调用全局action,与上述同理。
//同时,还可通过rootGetters访问全局getters
globalAction:{
root: true, //添加root:true属性,即可把globalAction注册为全局action,具体内容写在handler中
handler: (context, value) => { //虽然是全局action,但参数中的上下文context仍然是局部的
alert(context.state.info.name) //弹出应该是moduleA
}
}
},
mutations:{addMutation: (state, value) => {state.info.num += value}},
}
moduleB.js
export default {
namespaced :true,
state:{info:{name:'moduleB', num:300}},
getters:{
state: state => state ,
rootStateThroughModuleB:(state, getters, rootState, rootGetters) => {
//在getter中访问全局内容
//rootState为根状态,rootGetters为根getters
//通过root的来获得模块B的状态
return rootGetters['state']
// return rootState
}
},
actions:{addAction: (context, value) => {context.commit('addMutation', value, {root:true})}},
//context中包含dispatch, commit, getters, rootGetters
//在action中调用全局mutation,只需要在commit函数中添加第三个参数{root:true},这样第一个参数就会被当作全局内容来解析。
//在action中调用全局action,与上述同理。
//同时,还可通过rootGetters访问全局getters
mutations:{addMutation: (state, value) => {state.info.num += value}},
}
Test.vue:
<template>
<li> {{rootModule.name}}---{{rootModule.num}} <button @click="rootClick">rootAction---A++</button> </li>
<li> {{moduleA.name}}---{{moduleA.num}} <button @click="aClick">moduleAction---B++</button> </li>
<li> {{moduleB.name}}---{{moduleB.num}} <button @click="bClick">moduleAction---root++</button> </li>
<!-- 点击root,moduleA数字加一;点击moduleA,moduleB数字加一;点击moduleB,root数字加一 -->
<button @click="store.dispatch('globalAction')">触发全局action</button>
</template>
<script>
import {useStore} from 'vuex'
export default {
setup(){
let store = useStore()
console.log(store.getters);
let rootModule = store.getters['moduleB/rootStateThroughModuleB'].info //通过moduleB的getters获得root的状态
let moduleA = store.getters['moduleAStateThroughRoot'].info //通过root的getters获得moduleA的状态
let moduleB = store.getters['moduleA/moduleBStateThroughModuleA'].info //通过moduleA的getters获得moduleB的状态
// let moduleB = store.state.moduleB.info
// let moduleA = store.state.moduleA.info
// let rootModule = store.state.info
function rootClick(){store.dispatch('addAction', 1)} //调用root中的action,改变的是moduleA的状态
function aClick(){store.dispatch('moduleA/addAction', 1)}//调用moduleA中的action,改变的是moduleB的状态
function bClick(){store.dispatch('moduleB/addAction', 1)}//调用moduleB中的action,改变的是root的状态
return {rootModule,moduleA,moduleB,rootClick,aClick,bClick,store}
}
}
</script>
<style></style>
在模块中使用vuex
// import { State } from "@/type/test";
// import { useStore } from "vuex";
// const store = useStore()
/*
在模块中使用vuex时,上面的方式得到的store.state是undefined,下面这种方式才能正常得到state
不知道是什么原因
*/
import store from '../store'
export function increase(){
console.log(store.state)
}
3. vuex的typescript用法
vuex的typescript用法其实就是把state加上ts里的类型限制
3.1 不使用模块化
store文件
// store.ts
import { InjectionKey } from 'vue'
import { createStore, Store } from 'vuex'
// 为 store state 声明类型
export interface State {
count: number
}
// 定义 injection key
export const key: InjectionKey<Store<State>> = Symbol()
export const store = createStore<State>({
state: {
count: 0
}
})
在main.ts里引入
// main.ts
import { createApp } from 'vue'
import { store, key } from './store'
const app = createApp({ ... })
// 传入 injection key
app.use(store, key)
app.mount('#app')
在组件中使用useStore()获取store,将上述 injection key 传入 useStore 方法可以获取类型化的 store。
// vue 组件
import { useStore } from 'vuex'
import { key } from './store'
export default {
setup () {
const store = useStore(key)
store.state.count // 类型为 number
}
}
在每个组件中都导入key有些重复且麻烦,我们可以将useStore()封装成myUseStore()
// 在store.ts增加下面几行
import { useStore } from 'vuex'
export function myUseStore () {
return useStore(key)
}
3.2 使用模块化
store/index.ts
import { createStore, Store } from 'vuex'
import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'
import { InjectionKey } from 'vue'
//模块A state的类型
interface moduleAState {
name:string,
age:number
}
//模块B state的类型
interface moduleBState {
id:string,
adult:boolean
}
//vuex state类型
interface State{
moduleA:moduleAState,
moduleB:moduleBState
}
//如果使用模块化的话,在createStore参数里面就不要写state了,否则会报错
export const key: InjectionKey<Store<State>> = Symbol()
export const store = createStore<State>({
// state:{}, //不可以加这一行,否则报错
modules: {
moduleA,
moduleB
}
})
在组件中访问:
// vue 组件
import { useStore } from 'vuex'
import { key } from './store'
export default {
setup () {
const store = useStore(key)
store.state.moduleA// 类型为 moduleAState
store.state.moduleB// 类型为 moduleBState
}
}
待更新…
更多推荐
所有评论(0)