描述
之前用过react的ahook很舒服,但是vue的ahook功能太少。于是仿照react ahooks的useRequest 写的异步请求状态管理 加载更多,成功,加载中,失败
示意图
测试代码
<template>
<view>
<h2>自动触发</h2>
<view v-if="state.loading">loading</view>
<view v-else-if="state.error">error</view>
<view v-else>{{state.data}}</view>
<view>------------------------</view>
<h2>手动触发</h2>
<view v-if="manualState.loading">loading</view>
<view v-else-if="manualState.error">error</view>
<view v-else>{{manualState.data}}</view>
<button @click="manualState.run()">加载数据</button>
<view>------------------------</view>
<h2>局部刷新</h2>
<view v-if="hotState.loading">loading</view>
<view v-else-if="hotState.error">error</view>
<view v-else>
{{hotState.data}}
<text v-if="hotState.isRefresh">局部刷新中</text>
</view>
<button @click="hotState.reFresh()">局部刷新</button>
<view>------------------------</view>
<h2>加载更多</h2>
<view v-if="listState.loading">loading</view>
<view v-else-if="listState.error">error</view>
<view v-for="item in listState.data">{{item.name}}</view>
<view v-if="listState.loadingMore">加载中</view>
<view v-if="!listState.isThereMore">没有更多了</view>
<button @click="loadMore">加载更多</button>
<view>------------------------</view>
</view>
</template>
<script>
import {useRequest} from './useRequest'
import moment from "moment";
//模拟普通接口
function commonApi() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
code: 0,
result: moment().format("hh:mm:ss"),
message: 'success'
})
}, 1000)
})
}
//模拟列表
function listApi(data){
return new Promise((resolve, reject) => {
setTimeout(()=>{
resolve({
code:0,
message:"",
result:{
total:12,
records:[
{
name:'tang'+data,id:1
},
{
name:'tang',id:2
},
{
name:'tang',id:3
},
{
name:'tang',id:1
},
]
}
})
},1000)
})
}
//模拟调用接口
async function getCommonData() {
const e = await commonApi()
if (e.code === 0) return e.result
else throw e.message
}
//模拟调用接口
async function getListData(data) {
const e = await listApi(data)
if (e.code === 0) return e.result
else throw e.message
}
export default {
created() {
//如果参数依赖其他的data,则只能在data初始化之后操作
//pageNum没啥用 只是告诉下 带参数怎么使用
this.listState=useRequest(()=>getListData(this.pageNum),{loadMoreMod: true})
},
data() {
return {
state: useRequest(getCommonData),
manualState:useRequest(getCommonData,{manual:true}),
hotState:useRequest(getCommonData),
listState:"",
pageNum:1,
}
},
methods: {
loadMore(){
this.pageNum++
this.listState.loadMore()
},
}
}
</script>
<style>
</style>
useRequest 代码
/**
* 描述:异步状态管理
* 参数1 apiFunc
* 必须是一个Promise。
* 参数2 option
* manual:是否要手动出发,默认false,初始化时候就会调用一次接口,否则则需要自己手动调用run方法
* onSuccess:成功时候的回调,返回获取的数据
* onError:失败时候的回调,返回失败信息
* loadMoreMod 将以 加载更多 模式运行 将所有请求数据合并数组 .then(e)成功返回的数据必须有为{total:Number,records:[]},不是的自己await时候洗一下.
* 返回值
* 普通模式下
* let state = {
data: null, //返回的数据
loading: !manual, //是否正在加载
error: null, //错误信息
isRefresh: false, //是否正在局部刷新
run(params) ,调用接口,params是参数,会直接传给了apiFunc(params),当然如果你的apifunc不接受参数,那就当然没用了。
reFresh() 局部刷新。和run的区别就是,调用的时候不会将data重新置为null,而是在加载成功后替换掉。
}
加载更多模式下
let state = {
data: null, //返回的数据
loading: !manual, //是否正在加载
error: null, //错误信息
run(params) ,调用接口,params是参数,会直接传给了apiFunc(params),当然如果你传递apiFunc不接受参数,那就当然没用了。
isThereMore:是否还有更多的数据
loadingMore:是否正在加载更多中
isReloading:是否正在重新加载中,只是为了和loading状态区分开。
reLoad():重新加载
loadMore():加载更多
}
*/
function useRequest(apiFunc, option = {
manual: false,
onSuccess: false,
onError: false,
loadMoreMod:false,
}) {
const {manual, onSuccess, onError,loadMoreMod} = option
//通用状态
let state = {
data: null,
loading: !manual,
error: null,
isRefresh: false,
}
//如果开启了加载更多模式则添加【是否有更多数据状态】
if(loadMoreMod){
state.isThereMore=true
state.loadingMore=false
state.isReloading=false
}
//保存下查询的参数,列表带参数,调用reload后传递的参数保存下,给下次loadMore时候用。
let tempParams=""
const execute = (params,opt={isRefresh:false,isReload:false}) => {
const {isRefresh,isReload}=opt
apiFunc(params)
.then((e) => {
state.loading = false
//加载更多模式 合并请求的数据
if(loadMoreMod){
state.loadingMore=false
handleLoadMoreModData(e,isReload)
}
//普通模式
else {
state.data = e
if(isRefresh){
state.isRefresh=false
}
}
//如果是正在reload 关闭状态
if(isReload)
state.isReloading=false
if (onSuccess) {
onSuccess(e)
}
})
.catch((e) => {
state.error = e.toString()
state.loading = false
if(isRefresh){
state.isRefresh=false
}
uni.showToast({
title: '错误:' + e.toString(),
icon: "none",
duration: 2000
})
if (onError) {
onError(e.toString())
}
})
}
//加载更多模式下处理s tate.data 合并数据
const handleLoadMoreModData=(e,isReload)=>{
const {total,records}=e
//第二次之合并数据 如果不是reload
if(state.data&&!isReload){
//合并数据
state.data=[...state.data,...records]
//判断是否还有数据
const recordsLength=state.data.length
/*
* 为啥是大于等于呢,正常情况下等于是没问题的
* 但是第一次查10条,一共十二条,第二次查询的时候,突然删的只剩下一条数据了,那么返回的total是不是1 是不是就比当前合并的数组少了。
* */
if(recordsLength>=total)
state.isThereMore=false
}
//第一次直接赋值
else{
//如果有值
if(total!==0)
state.data=records
else{
state.data=[]
state.isThereMore=false
}
}
}
//普通执行 会清空之前查询的数据 相当于reLoad 、
const run = (params) => {
if(loadMoreMod){
state.loadingMore=false
state.isThereMore=true
state.isReloading=false
tempParams=params//加载更多模式保存查询参数,给reload,和loadMore用
}
state.error = null//重置错误状态
state.loading = true//加载状态改为加载中
state.data = null//重置之前查询的数据
//执行
execute(params)
}
//局部刷新
const reFresh=()=>{
state.isRefresh = true//加载状态改为加载中
//执行
if(tempParams)
execute(tempParams,{isRefresh: true})
else
execute(undefined,{isRefresh: true})
}
//加载更多
const loadMore=()=>{
//如果还要更多的数据,而且当前不是正在加载更多的状态
if(state.isThereMore&&!state.loadingMore){
state.loadingMore=true
//执行
//如果有保存的参数,则传递保存的参数
if(tempParams)
execute(tempParams)
else
execute()
}
}
//重新加载
const reload=()=>{
state.loading=false
state.error=null
state.loadingMore=false
state.isThereMore=true
state.isReloading=true
//执行
if(tempParams)
execute(tempParams,{isReload:true})
else
execute(undefined,{isReload:true})
}
//如果手动触发为false,则直接run查询
if (!manual) run()
state.run=run
//加载更多模式不适用局部刷新
if(!loadMoreMod){
state.reFresh=reFresh
}
//只有加载更多模式下才有的reload 和loadMore
if(loadMoreMod){
state.reload=reload
state.loadMore=loadMore
}
return state
}
export {
useRequest
}