actions的基本使用
当我们想把异步操作,移动到vuex中时,但是mutations并不能帮你解决异步操作。所以vuex帮我们多增加了一层:Actions层。通过actions中,完成异步操作,然后再在actions中commit提交给Mutations中,然后再通过Mutations来修改State中的数据。
index.js
组件先使用actions,然后通过actions自己调用了mutations,最后mutations来修改state
state(){
counter:100
},
actions:{
//放函数
incrementAction(context){
//通过context来提交给Mutations
context.commit('increment')
}
},
mutations:{
increment(state){
state.counter++;
}
},
Home.vue
<template>
<div>
<button @click="increment">+1</button>
</div>
</template>
<script>
export default{
methods:{
increment(){
//commit提交 是mutations
//这里 dispatch分发 是actions
this.$store.dispatch("incrementAction")
},
setup(){
}
}
}
</script>
运行结果
执行异步操作(使用actions)
上面的例子并不能看出actions的作用。下面举例:
我希望我点击按钮的时候,延迟一秒+1
index.js
state(){
counter:100
},
actions:{
incrementAction(context){
//这里进行异步操作
setTimeout(()=>{
context.commit('increment')
},1000)
}
},
mutations:{
increment(state){
state.counter++;
}
},
运行结果
异步操作高级应用
使用axois的前提:安装他npm install axois
Home.vue
<template>
<div>
<button @click="increment">+1</button>
</div>
</template>
<script>
//引入axois
import axios from 'axios'
export default{
methods:{
increment(){
this.$store.dispatch("incrementAction")
},
},
//我们不希望一些请求在组件里完成,而是在vuex中
//所以这里网络请求dispatch分发给actions处理
mounted(){
this.$store.dispatch("getHomeMuldata",res.data.banner.list)
},
}
}
</script>
index.js
state(){
counter:100,
//网络请求后得到的数据,初始化
banners:[]
},
actions:{
incrementAction(context){
setTimeout(()=>{
context.commit('increment')
},1000)
},
getHomeMuldata(context){
//在这里进行网络请求,get返回一个promise
axois.get("服务器地址").then(res=>{
this.$store.commit("addBannerdata",res.data.banner.list)
}
}
},
mutations:{
increment(state){
state.counter++;
},
//操作网络请求后得到的轮播图数据
addBannerData(state,payload){
//将网络请求得来的数据存到仓库banners中
state.banners = payload
}
},
请求后得到的数据(服务器里的数据):
运行结果:(服务器里的banner.list存入仓库中)
actions参数
Home.vue
methods:{
increment(){
//除了分发给mutations的函数外,自己还传了一个对象
this.$store.dispatch("incrementAction",{
count:100,
})
}
}
index.js
actions:{
//自己书写的对象参数,actions中 payload接收
incrementAction(context,payload){
console.log(payload)//{count:100}
setTimeout(()=>{
context.commit('increment')
},1000)
console.log(context)
}
},
actions中的context参数存在的方法:
commit方法:很明显就是从我actions的context里面,拿到commit方法,提交给mutation
dispatch方法:我可能会在我这个action中调用actions中的其他action
这个时候就可以用context.dispacth("action")
getters方法:在actions中获取到getters里的info属性
context.getters.info
rootGetters方法:分模块才会使用到
rootState方法:分模块才会使用到
state方法:通过context.state.拿到state中具体的数值
分发的第二种写法
Home.vue
methods:{
decrement(){
this.$store.dispatch({
//分发的方法也可以写成对象形式
//这里写的就是分发给actions中的函数
type:"decrementAction"
})
}
}
actions中的辅助函数(optional API)
Home.vue
<template>
<button @click="incrementAction">+1</button>
<button @click="add">+1</button>
</template>
<script>
import {mapActions} from 'vuex'
export default:{
methods:{
//不用再书写一个方法,然后里面还要dispatch
//直接利用辅助函数分发给action
数组写法 ...mapActions(["incrementAction"])
对象写法 ...mapActions({
//对这个方法重新命名了
add:"incrementAction"
})
}
}
</script>
actions中的辅助函数(composition API)
Home.vue
<template>
<button @click="incrementAction">+1</button>
<button @click="add">+1</button>
</template>
<script>
import {mapActions} from 'vuex'
export default{
setup(){
//数组
const actions = mapActions(["incrementAction"])
//对象
const actions2 = mapActions({
add:"incrementAction"
})
//解构
return{
...actions,
...actions2
}
}
}
</script>
因为actions中存储的本来就是方法,所以不需要用computed包裹成一个ref对象
Composition API使用actions
Home.vue
<template>
<div></div>
</template>
<script>
import {onMounted} from 'vue'
import {useStore} from 'vuex'
export default{
setup(){
//因为mounted不是Composition API
//所以这里使用onMounted
onMounted(()=>{
//通过引入usestore才能拿到store
const store = useStore()
onMounted(()=>{
store.dispatch("getHomeMuldata")
})
})
}
}
</script>
index.js(和上方的index.js一样保持不变)
actions:{
getHomeMuldata(context){
axois.get("服务器地址").then(res=>{
this.$store.commit("addBannerdata",res.data.banner.list)
}
}
},
actions细节补充
actions中是没有返回值的,因为它里面存储的都是函数,返回值其实是一个undefined
但是我actions中的每个函数是可以有一个Promise()的返回值的。return new Promise()
这意味着,我在组件里dispatch的时候可以接收返回的值
const promise = store.dispatch("increment")
此时就可以监听promise的then,知道什么内部时候执行结束,也可以传输数据,也可以拿到你的错误信息
Home.vue
setup(){
const store = useStore()
onMounted(()=>{
//获取到getHomeMuldata返回的结果值
const promise = store.dispatch("getHomeMuldata")
//如果我的返回结果是true,我就输出promise中正确的信息
promise.then(res=>{
console.log(res);//成功啦美汁汁
}).catch(err=>{
//返回结果报错了我就捕获他,打印错误信息
console.log(err)
})
})
}
index.js
actions:{
getHomeMuldata(context){
return new Promise((resolve,reject)=>{
//这里是resolve成功的结果
axois.get("服务器地址").then(res=>{
this.$store.commit("addBannerdata",res.data.banner.list)
//成功的话,我就回调Home.vue中的then
resolve("成功啦美汁汁")
}).catch(err=>{
reject(err)
}
})
})
}
}
模块modules的使用
如果我的项目非常的庞大,有很多的功能。vuex作为一个仓库,公共的数据全部存储在其中。但是这样还是不够完善。
我在设计首页Home的时候,Home组件里面一定会再包含着子组件,子组件里可能还会包含子组件,当我的其他页面比如 登录页面、商品页面肯定也会有很多子组件。
当我把这些大组件的所有公共的数据全部存储在一个vuex中,就会造成数据使用的错误(如果存在同名的情况,举个例子而已)
我们希望所有和Home组件有关的子组件,他们使用的公共数据存储在一块;和登录大组件有关的一些若干子组件,他们使用的公共数据又存储在其他地方。
模块里面又可以分为若干个子模块,不过实际上一个vuex里分为若干模块就可以了,再在若干模块里面又分为若干子模块就有点麻烦了。
但是vue官方并不推荐我们创建多个store对象,这个时候就需要使用 modules分模块 进行对相同类型组件的公共数据的管理了。
模块的演练
模块的本质就是一个对象
export default ____
使用default,后续引用的时候,可以不加 大括号{}
直接export ___,引用的时候需要加上大括号{}。
export default暴露的成员可以用任意变量来接收,所以下方我import进来的名字可以随便取,不用和export出去的名字相同
src/modules/home.js
const homeModule = {
state(){
return{
homeCounter:100
}
},
getters:{
},
mutations:{
},
actions:{
},
}
export default homeModule
src/modules/user.js
const userModule = {
state(){
return{
userCounter:10
}
},
getters:{
},
mutations:{
},
actions:{
},
}
export default userModule
index.js 最大的公共仓库
import {createStore} from 'vuex'
import home from './modules/home'
import user from './modules/user'
const store = createStore({
state(){
return{
rootCounter:0
}
},
mutations:{
increment(state){
state.rootCounter++
}
},
//必须还要在大的vuex仓库中,进行modules声明
modules:{
//key:value,如果key和value可以简写
home:home
user
}
})
export default store
使用vuex
Home.vue
<template>
<div>
//如果这样写的话,取到的肯定是根的
{{$store.state}}
//根据modules中的key来取,这样写是因为这样设计的
{{$store.state.home.homeCounter}}
{{$store.state.user.userCounter}}
</div>
</template>
<script>
import
export default{
setup(){
}
}
</script>
module的局部状态
src/modules/home.js
const homeModule = {
state(){
return{
homeCounter:100
}
},
getters:{
doubleHomeCounter{
return homeCounter*2
}
},
mutations:{
increment(state){
state.homeCounter++
}
},
actions:{
incrementAction(context){
context.commit("increment")
}
},
}
export default homeModule
Home.vue
<template>
<div>
<button @click="homeIncrement">home+1</button>
//取home.js中的getters
//这个getters中的方法在哪个模块中没有明确指出,所以存在隐患
{{$store.getters.doubleHomeCounter}}
<button @click="homeIncrementAction">
</button>
</div>
</template>
<script>
import
export default{
methods{
homeIncrement(){
//使用home.js模块里的mutations中的方法
this.$store.commit("increment")
},
homeIncrementAction(){
this.$store.dispatch("incrementAction")
}
}
}
</script>
但是如果我的大仓库index.js也有一个increment呢,当我使用this.$store.commit("increment")的时候,其实两个地方的increment方法都会执行,只要你提交的方法名在他们俩的mutations中存在。
当我派发 this.$store.dispatch("incrementAction")的时候,他也是不知道我派发的是给哪一个模块里的actions,没有办法区分
如何避免这种问题?
home.js
const homeModule = {
在这里为他加上命名空间就可以避免上述问题
namespaced:true,
}
后续Home.vue调用homeModule:
home模块下的getters下的doubleHomeCounter方法
{{$store.getters["home/doubleHomeCounter"]}}
以前的写法:对比
{{$store.getters.doubleHomeCounter}}
getters的补充(home.js)
getters:{
doubleHomeCounter(state,getters,rootState,rootGetters){
return state.homeCounter*2
//通过第二个参数拿到getters中的其他方法
getters.otherGetter()
//通过第三个参数获取根的state
rootState.indexStateCounter
//通过第四个参数获取根的getters
},
otherGetter(state){
return 100
}
},
actions:{
incrementAction(context){
context.commit
context.dispatch
context.state
context.rootState
context.getters
context.rootGetters
//null:表示不传payload
//root:true表示传给root中mutations的increment
context.commit("increment",null,{root:true})
//再分发给根root里面actions中的increment
context.dipatch("increment",null,{root:true})
}
}
module的辅助函数
optional API写法
Home.vue
<template>
<div></div>
</template>
<script>
import{mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default{
//vue2中直接将mapState和mapGetters包裹
computed:{
//写法一:
...mapState({
homeCounter:state=>state.home.homeCounter
}),
...mapGetters({
doubleHomeCounter:"home/doubleHomeCounter"
})
}
computed:{
//写法二:
...mapState("home",["homeCounter"]),
...mapGetters("home",["doubleHomeCounter"])
},
---------------------------------------------------
methods:{
//写法一
...mapMutations({
increment:"home/increment"
}),
...mapActions({
incrementAction:"home/incrementActions"
})
}
methods:{
//写法二:
...mapMutations("home",["increment"]),
...mapActions("home",["incrementAction"])
}
---------------------------------------------------
}
</script>
还有第三种写法,较为特殊,不用自带的mapState,更为常用
Home.vue
<template>
<div></div>
</template>
<script>
//引用一个createNamespaceHelpers import{createNamespaceHelpers} from 'vuex'
//放入对应的模块,告诉他我们用的全是home模块
const {mapState,mapGetters,mapMutations,mapActions} =
createNamespaceHelpers("home").mapState
export default{
computed:{
//第三种方法
...mapState(["homeCounter"])
...mapGeters(["doubleHomeCounter"])
------------------和之前的对比
...mapGetters({
doubleHomeCounter:"home/doubleHomeCounter"
})
},
methods:{
...mapMutations(["increment"])
...mapActions(["incrementAction"])
------------------和之前的对比
...mapMutations("home",["increment"]),
}
}
</script>
Composition API写法
Home.vue
<template>
<div></div>
</template>
<script>
export default{
setup(){
//{homeCounter:function}
const state = mapState(["homeCounter"])
const getters = mapGetters(["doubleHomeCounter"])
const mutations = mapMutations(["increment"])
const actions = mapActions(["incrementAction"])
return{
//直接解构返回的是一个函数,是会报错的
...state
...getters
//这两个本身使用的时候就是使用函数,所以解构使用无所谓
...mutations
...actions
}
}
}
</script>
导入我们曾经写的hook函数,用来解构mapState和mapGetters
import {useState,useGetters} from '../hooks/index'
但是我们写的hook函数使用只能调用根vuex中的State和Getters,我们还需要对hook函数进行相应的改进才能真正使用在模块中。
useState.js
//当我们使用.useState("home",['HomeCounter'])里面可能会写具体哪个模块里的哪个方法
如果我们还用mapState的,他是认不出来的,所以要额外导入createNamespacedHelpers。
import {mapState,createNamespacedHelpers} from 'vuex'
import {useMapper} from './useMapper'
//我们多添加一个参数,用来传模块的名字
export function useState(moduleName,mapper){
let mapperFn = mapState
//对moduleName处理,可能也会不传,所以要先写个if
if(typeof moduleName === 'string' && moduleName.length>0){
//将具体模块名里的具体方法,赋值给mapperFn
mapperFn = createNamespaceHelpers(moduleName).mapState
} else{
//我们可能直接传mapper,没有传Name
但是接收参数会默认将第一个视为Name,也就是说Name接收到了mapper的值
所以我这里再将mapper值重新赋一下
mapper = moduleName
}
//我们先前用createNamespaceHelpers告诉他到底是哪来的方法
//通过赋值给他,这样他就知道到底是哪个模块中的方法了
return useMapper(mapper,mapperFn)
}
useGetters也是同样的道理
import {mapGetters,createNamespaceHelpers} from 'vuex'
import {useGetters} from './useGetters'
export function useGetters(moduleName,mapper){
let mapperFn = mapGetters
if(typeof moduleName === 'string'&&moduleName.length>0){
mapperFn = createNamespaceHelpers(moduleName).mapGetters
} else{
mapper = moduleName
}
return useMapper(mapper,mapperFn)
}
使用:
<script>
export default{
setup(){
//{homeCounter:function}
const state = useState(["home","homeCounter"])
const getters = useGetters(["home","doubleHomeCounter"])
const mutations = mapMutations(["increment"])
const actions = mapActions(["incrementAction"])
return{
...state
...getters
...mutations
...actions
}
}
}
</script>
nexttick
App_nexttick.vue
每次点击button,message都会增加内容
我希望每次点击完button,能够显示<h2 class="title">{{message}}</h2>标签的高度
<template>
<div>
<h2 class="title" ref="titleRef">{{message}}</h2>
<button @click = "addMessageContent">
添加内容
</button>
</div>
</template>
<script>
import {ref} from 'vue'
export default{
setup(){
const message = ref("123123")
const titleRef = ref(null)
const addMessageContent=()=>{
message.value+="阿巴"
//拿到这个DOM的高度
console.log(titleRef.value.offsetHeight)
}
return{
message,
addMessageContent
}
}
}
</script>
<style scoped>
.title{
width:120px;
}
</style>
实际上返回的结果是:每一次点击按钮前 DOM的高度;我的要求是先更改掉界面的值,然后再获取最新的高度。并不是这样的结果。
我们最先想到的解决方法就是将获取DOM高度的操作,放在onUpdated生命周期函数中,每次DOM更新后,我就执行,不然不执行。
但是这样会带来局限性:如果我页面中才存在其他需要更新DOM的操作,我每次执行这些操作的时候,我存储在onUpdated生命周期函数里的获取高度的函数,他每次都要执行一次,这不纯纯的bug吗?
解决方法:使用nexttick
他每次执行都是在DOM更新后