序言
Vuex的五大核心
1.state
2.getters
3.mutations
4.actions:弥补mutations不能异步操作的功能
5.modules:对store状态划分模块
Vuex的状态管理
在开发中,我们的应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据的管理,我们称之为 “状态管理”
data或setup中的数据:state
template模块中的数据最终被渲染成DOM:View
在模块中处理这些行为事件,会修改state:actions
复杂的状态管理(前面的是简单的状态管理)
把状态抽离出去,统一管理,这就是Vuex背后的思想
State:存放各种数据
Vue Components:组件,任意组件都可以使用State数据,$store.state.name来使用
Mutations:Vuex不允许随便修改。我使用按钮点击来修改值,然后提交一个mutation-->commit mutation,来修改State中的值
可以直接从Vue Components组件里提交Mutations,跳过Actions。mutation:{函数1,函数2}
Mutation不允许有异步请求,而是在Actions中完成操作。
为什么Mutation不允许有异步请求?
同步:等到前面的任务执行完成后,我才能执行下一个任务,所以同步没有并行的概念
异步:两个任务可以同时执行,我可以边执行A也可以边执行B
我们使用devtools时,devtools可以帮助我们捕捉mutations的快照,但如果速速异步操作,那么devtools将不能很好的追踪这个操作什么时候会被完成。结果就会导致devtools记录的数据没有与页面显示的数据保持一致。
Vuex的具体演练
devtools方便我们对组件、vuex进行调试。他是chrome的一个插件
安装vuex
npm install vuex@next
store文件夹,专门存放vuex
store/index.js
//导入createStore函数
import {createStore} from 'vuex'
//调用该函数
const store = createStore({
state(){
//公共数据,存放在仓库里,任何组件都可以用
return{
counter:0
}
},
mutations:{
//这些函数被调用的时候会传来一个state
increment(state){
state.counter++
}
}
})
export default store
main.js
import store from './store'
createApp(App).use(store).mount('#app')
App.vue
使用仓库数据
<template>
<h2>{{$store.state.counter}}</h2>
</template>
<script>
export default{
methods:{
increment(){
//这里要加 this,不允许在这里直接操作state里的数据
this.$store.state.counter
//而是commit提交mutation,由mutation修改State数据
//commit("mutations中的方法名")
this.$store.commit("increment")
}
}
}
</script>
当我App.vue中想要使用并修改state中数据的时候,我可以在仓库中建立一个mutations来存放想要使用发的方法,在这里我们就可以来修改state中的数据,完成相应的逻辑代码。然后再App.vue中的对应方法中,向仓库提交 想使用仓库的某个方法名,最后由仓库mutations的方法来完成操作。
单一状态树
就是说:我可以创建多个const store1 = create{},然后在给他们一起export出去,但是vue不推荐我们这样。而是用一个对象包含全部的应用层级的状态。只会有一个store,但是给他模块化,所以不用担心混乱。
useState
当我们通过{{$store.state.counter}}来拿仓库中的数据,在template中要写很长的表达式,我们可以通过计算属性方式来拿
{{sCounter}}
//通过hook拿到this.$store
import {mapState,useStore} from 'vuex'
import {computed} from 'vue'
setup(){
const store = useStore()
const sCounter = computed(()=>{
store.state.counter
})
return{
sCounter
}
}
}
但是一旦数据多了,用计算属性还是会比较麻烦,此时我们可以使用辅助函数 mapState,对我们的State进行一个映射,本质还是通过计算属性来获得,但是要比我们直接对计算属性操作要方便点。只是换了个形式
vue2.0中的写法:
{{counter}}--{{name}}--{{age}}
export default{
computed:{
...mapState(["counter","name","age"])
}
//通过数组里传入的key来获取state中的value
//computed返回值是一个ref对象
//mapState是一个函数,里面的参数可以是数组也可以是对象。他的返回值也是一个对象
//它的内部实际上是这样的
{
counter:function(){},
name:function(){}
}
}
import {mapState,useStore} from 'vuex'
export default{
setup(){
const store = useStore()
//01对象形式的函数,存在问题
const storeState = mapState(["counter","name","age"])
//{name:function,age:function}
return{
//02将storeState对象进行解构,然后再template中展示
//实际上是对函数进行了展示,并不是我们想要的内容
...storeState
}
}
}
}
如何解决返回的是对象里面存的是函数的问题?
Object.keys() 是用于获得由对象属性名组成的数组
Object.keys(person).forEach(function(trait) {
//trait:对象名,person[trait]:value
console.log('Person ', trait,': ', person[trait]);
});
让她被computed包裹即可,computed要求你传入一个函数,最终返回一个ref值
{{counter}}
setup(){
const store = useStore()
//内部存的形式是:{name:function,age:function}
const storeStateFns = mapState(["counter","name","age"])
const storeState = {}
//通过遍历storeStateFns中的key得到里面的函数
//并将函数赋值给fn,将获取到的fn被computed包裹
//包裹后的生成了对象并被computed变成一个ref形式的
//{name:ref,age:ref}响应式
//赋值后生成的新对象,里面存放对应的value
//Object.keys() 返回的正是一个对象属性[数组]
Object.keys(storeStateFns).forEach(fnKey=>{
//fnKey:是对象名key,storeStateFns[fnKey]:里面存的function也就是value
const fn = storeStateFns[fnKey]
//被computed包裹后返回一个对象,不再是函数
//然后通过对象的key拿到相应的value的ref值
storeState[fnKey] = computed(fn)
})
return{
...storeState
}
}
但是上方的代码仍然存在一个问题:不能读取$store属性
...mapState(["name","age"])计算属性帮我们读取数据的时候,
他会通过this.$store.state.name来读取,但是在setup中已经没有this指向了,const fn = storeStateFns[fnKey]里的function的this是一个undefined,所以在computed(fn)会报找不到$store的错误。
const store = useStore()
const storeStateFns = mapState(["counter","name","age"])
const storeState={}
/*
调用函数的bind方法,每个函数都有一个bind
里面要有属性,存放对象,这里的store是useStore的store
给storeStateFns[fnKey]绑定了一个this,赋值给了fn
当我们执行了storeStateFns[fnKey],他的this就是$store里的store
*/
Object.keys(storeStateFns).forEach(fnKey=>{
const fn = storeStateFns[fnKey].bind({$store:store})
storeState[fnKey] = computed(fn)
})
return{
...storeState
}
要么使用这种方法,要不然就老老实实的直接写const sCounter=computed(()=>store.state.counter)
然后return出去,然后就可以直接在template中直接使用名字
封装
对刚刚的方法进行封装,这样以后使用的时候直接调用即可
hook就是函数,我们习惯将setup中需要封装的函数成为hook
src/hooks/useState.js
import {mapState,useStore} from 'vuex'
import {computed} from 'vue'
export function useState(mapper){
//拿到store独享
const store = useStore()
//获取到响应对象的functions:{name:function,age:function}
const storeStateFns = mapState(mapper)
//对数据进行转换,获取到ref的数据
const storeState = {}
Object.keys(storeStateFns).forEach(fnKey=>{
const fn = storeStateFns[fnKey].bind({$store:store})
storeState[fnKey] = computed(fn)
})
return storeState
}
Home.vue使用hooks
<template>
<div>
{{counter}}--{{name}}--{{age}}
{{sCounter}}--{{sName}}
</div>
</template>
<script>
import {mapState,useStore} from 'vuex'
import {computed} from 'vue'
import {useState} from '../hooks/useState'
export default{
setup(){
//想用仓库里的什么,就在这里传什么
const storeState = useState(["counter","name"])
//传对象
//可能我的setup中也有名字叫counter的
这个时候使用对象的形式对state中也叫counter的取别名
const storeState2 = useState({
sCounter:state=>state.counter,
sName:state=>state.name
})
return{
//解构出来所有的属性
...storeState
...storeState2
}
}
}
</script>
为啥我写了一个hook,居然直接支持对象和数组的两种形式呢?因为我实际上在hook中通过const storeStateFns = mapState(..)的mapState来处理,mapState本身就支持对象和数组的形式,所以无可厚非。
getters的基本使用
我想要得到每本书的销售额,如果和以前一样获取的话,需要在自己的页面,通过computed来获取。但是我们通过使用getters,对一些需要经过变化的数据来操作使用,以后我不管在哪个页面,都可以很方便的获取到。
store/index.js
import {createStore} from 'vuex'
const store = createStore(){
state(){
return{
counter:100,
name:"why",
age:18,
books:[
{name:"1",price:200,count:3},
{name:"2",price:220,count:1},
{name:"3",price:240,count:4},
{name:"4",price:250,count:6},
],
discount:0.6,
}
},
mutations:{},
getters:{
//当我的getters又依赖其他的getters,就可以使用他的第二个参数getters
totalPrice(state,getters){
let totalPrice = 0
for(const book of state.books){
totalPrice += book.count * book.price
}
//就可以通过getters参数取到getters中的其他
return totalPrice * getters.currentDiscount
},
currentDiscount(state){
//在之前的折扣里,又折上折
return state.discount*0.9
},
//只计算数量大于n的书籍
totalPriceCountGreaterN(state,getters){
return function(n){
let totalPrice = 0
for(const book of state.books){
if(book.counter > n){
totalPrice += book.count * book.price
}
}
//对应 function 函数
return totalPrice * getters.currentDiscount
}
}
}
}
使用getters
//getters里是函数,但是里面会返回具体的值。展示返回值
{{$store.getters.totalPrice}}
//对getters里函数中又返回了一个函数并且是一个带参函数 的使用
{{$store.getters.totalPriceCountGreaterN(2)}}
如果不写()而是直接$store.getters.totalPriceCountGreaterN返回的实际上是一个函数,因为他内部就是return了一个函数,而加上了括号表示调用了这个函数,调用后会返回一个值,所以就可以正常展示数据
我们发现,在template中,书写getters的时候,表达式有点过长{{$store.getters.totalPriceCountGreaterN(2)}}为什么我们过分追究表达式过长这个问题?因为在{{}}的里面,我们不推荐过长的表达式,解决方案:
optional API的写法
{{nameInfo}}
export default{
computed:{
nameInfo(){
return this.$store.getters.nameInfo
}
}
}
上面这种方式仍然很长,所以我们使用更优的:通过辅助函数
{{nameInfo}}--{{ageInfo}}
{{sNameInfo}}
import {mapGetters} from 'vuex'
export default{
computed:{
//传key,最终解构后得到value
...mapGetters(["nameInfo","ageInfo"])
//对象的写法
...mapGetters({
//直接写名称就行了,和mapState有所区别
sNameInfo:"nameInfo"
})
}
}
composition API写法
<div>
{{sNameInfo}}
</div>
import {computed} from 'vue'
import {mapGetters,useStore} from 'vuex'
export default{
setup(){
const store = useStore();
//直接通过computed拿到
const sNameInfo = computed(()=>{
store.getters.nameInfo
})
return{
sNameInfo
}
}
}
使用Getters
先将实现Getters的方法抽取成一个hook,方便日后调用
/hooks/useGetters.js
import {mapGetters,useStore} from 'vuex'
import {computed} from 'vue'
export function useGetters(mapper){
//拿到store独享
const store = useStore()
//获取到响应对象的functions:{name:function,age:function}
const storeGettersFns = mapGetters(mapper)
//对数据进行转换,获取到ref的数据
const storeGetter = {}
Object.keys(storeGettersFns).forEach(fnKey=>{
const fn = storeGettersFns[fnKey].bind({$store:store})
storeGetter[fnKey] = computed(fn)
})
return storeGetter
}
<div>
{{sNameInfo}}
</div>
import {useGetters} from './hooks/useGetters'
export default{
setup(){
const storeGetters = useGetters(["nameInfo","ageInfo"]);
return{
...storeGetters
}
}
}
大家发现,我的useGetters和useState的逻辑代码基本一样,只是一个用了mapGetters方法,一个用了mapState方法,但是我却要再写一遍,所以我们可以对这种代码再做一层封装。
/hooks/useMapper.js
import {mapGetters,useStore} from 'vuex'
import {computed} from 'vue'
//名字需要被修改
export function useMapper(mapper,mapFn){
const store = useStore()
//由传进来的mapFn来决定使用State还是Getters
const storeMapperFns = mapFn(mapper)
const storeMapper = {}
Object.keys(storeMapperFns).forEach(fnKey=>{
const fn = storeMapperFns[fnKey].bind({$store:store})
storeMapper[fnKey] = computed(fn)
})
return storeMapper
}
由useState和useGetters来调用useMapper
/hooks/useState.js
import {mapState} from 'vuex'
import {useMapper} from './useMapper'
export function useState(mapper){
//调用useMapper并且返回结果
return useMapper(mapper,mapState)
}
/hooks/useGetters.js
import {mapGetters} from 'vuex'
import {useMapper} from './useMapper'
export function useGetters(mapper){
//调用useMapper并且返回结果
return useMapper(mapper,mapGetters)
}
其他页面正常引入useGetters和useState,然后将需要的值传进去,通过这两个方法再调用useMapper,最终得到结果
Mutation基本使用
我想通过鼠标点击btn然后修改store的值
就得通过提交,由vuex中的mutations中的increment来实现
<template>
<button @click = "store.commit('increment')"></button>
</template>
state:{
counter:100
},
mutations:{
increment(state){
state.counter++
}
}
mutation中有两个参数,除了state还有一个payload,就是我从自己地方commit提交的时候,可以自己携带一个数据传入
Home.vue中
methods:{
addTen(){
//这里10就被提交后,变成了第二个参数payload
this.$store.commit('incrementN',10)
//真实开发中,我不可能只传一个数,如何解决传入多个数?
//payload写成对象形式
this.$store.commit('incrementN',{
n:10,
name:"why",
age:20
})
//另外一种提交风格,是等价的
this.$store.commit({
//将提交的函数写成了type
type:'incrementN',
n:10,
name:"why",
age:20
})
}
}
在store/index.js中
mutations:{
incrementN(state,payload){
state.counter += payload
}
//获取对象形式的payload,从对象里拿到具体属性
incrementN(state,payload){
state.counter += payload.n
}
}
mutation辅助函数
我们每一次都需要$store.commit('..')来提交,我们又双叒叕嫌弃他太长了,我们就可以使用辅助函数了。
<template>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
//我像incrementN里面再传入一个参数
<button @click="incrementN({n:10})">-1</button>
<button @click="add">+1</button>
</template>
<script>
import {mapMutations} from 'vuex'
export default{
methods:{
//里面的参数都是mutations里的函数,就不用再写提交了
...mapMutations(["increment","decrement","incrementN"])
//对象类型的写法
...mapMutations({
//改变名字
add:"increment"
})
}
}
</script>
以上全是在optional API中
下面是在composition API中书写
setup(){
const storeMutations = mapMutations(["increment","decrement","incrementN"])
//为什么他可以直接return解构呢?
//因为我这个是被绑定在button的@click的
它本身就要返回一个函数,我就是要函数,我就不用再把他转换成数据了
return{
...storeMutations
}
}