处理异步请求是前端应用逻辑中最常见的需求之一。在前端应用中,每个异步请求除了自身状态,还有伴随状态和方法,如请求返回数据,Loading状态,错误处理,重置请求数据等等。使用统一的数据管理(Model)方案,在请求数量增多后,在Model中就会存在大量的请求伴随状态,伴随状态又需要根据请求状态更改,这增加了很多重复劳动。
虽然市面上已经有了不少这个问题的解决方案,比如React Query,RTK Query,自定义hooks等,但配合Mobx State Tree的解决方案却很少,只有官方网站上提到mst-query。不过,mst-query学习成本很高,基本改变了原来Mobx State Tree的使用方式,在Mobx State Tree学习曲线已经不太友好的情况下,我并不倾向于再引入更多的复杂度。
所以,为了简单的解决这个常见问题,我用Mobx State Tree中自定义model type的思路,制作了一个简单的自定义type,力图最小化学习、使用成本,只提供异步请求必需的伴随功能,并尽可能为使用者提供灵活度。这个库非常小,只有4个必须使用的API,但是可以减少大部分的异步请求处理成本,Mobx State Tree的使用者可以尝试一下。
链接
安装
npm i mst-request-type -S
注意:需要自行安装mobx-state-tree的5.0.0以上版本。
使用
import Request from 'mst-request-type'
在MST model中声明Request类型:
types.model('NAME').props({ ..., asyncData: Request, ... })
asyncData对应一个需要从异步请求获取的数据
在React中使用:
读取props:<div>{asyncData.PROPS}</div>
调用actions: asyncData.ACTIONS()
可以看出,mst-request-type提供了一个custom model type(文中import成Request),异步请求需要的属性和方法都在这个type上。
示例
// request function
const getCat = async ({ controller }) => {
...
return output
}
// Declare model
import Request from 'mst-request-type'
const Cat = types
.model('Cat')
.props({
cat: Request,
})
.actions(self => ({
getCat: flow(function* () {
self.cat.set(SCat.getCat)
yield self.cat.fetch()
}),
}))
// Access in React
const App = () => {
const { Cat: model } = useStore()
useEffect(() => {
model.getCat()
}, [])
return (
<div>
<h1>Cat</h1>
<div>{model.cat.loading.toString()}</div>
{model.cat.data}
<button onClick={() => model.cat.refetch()}>One more</button>
</div>
)
}
export default observer(App)
APIs
常用的API只有4个:loading,data,set(request, reject?),fetch(params?)
props(属性)
绝大多数情况下,只需要用到
loading和data
status
请求的当前状态,共有'init', 'pending', 'success', 'error', 'canceled' 5种
loading
一个根据status得到的计算值,在status是pending的时候返回true
data
请求返回数据,数据由请求函数返回,默认为[],data数组中的对象是非observable,也就是说对于Mobx State Tree而言,data整体是一个leaf节点,不是一颗子树
error
请求发生的错误,由请求函数抛出
token
一个可选的id,默认是'',可以通过option()设定,目前没有什么作用,Request实例由创建Request的model追踪。
actions(方法)
绝大多数情况下,只需要用到
set()和fetch()
set(request, reject?)
必须在fetch()之前调用,以设定一个request函数供fetch使用,否则调用fetch()时会报错
设置该Request type使用的request函数和错误处理函数,两个函数都会在后面的fetch()方法中调用。
每次调用set()都会调用reset()重制该Request中的data,error,status。
如果在option()中设定了once = true,set()只能调用一次。
这里多说一句为什么没有把set的功能直接做在create上。因为设计的思路是自定义的model type,如果初始化做到create上,在调用时会和其他的type产生比较明显的差异。另外,set本身也不可或缺,默认情况下,request函数是可以被修改的,所以采用了当前的设计方式。
fetch(params?)
默认params为{}
传入参数params调用set()设定的request函数。调用过程中会根据请求状态改变Request的status,data,error props,如果也设定了reject handler,则会被用来处理请求本身抛出的错误。
请求成功后,返回的数据会存在data属性中,请求错误,data是[],error存在error属性中。
请求的参数会被保存在Request的实例中,如果fetch()没有传入参数,则会使用上一次的参数。
建议request函数始终返回数组保持一致性
reset()
清除data和error属性,并将status设为'init'
setCancel(cancel: AbortController)
为该Request设定一个Abort controller用于终止请求。如果没有参数,则会默认设定一个新的Abort controller,供后面的cancel()使用。
注意:调用过setCancel()就会为该Request设定一个自己的Abort controller,这个controller会以{ controller }参数的形式传递给request函数,如果需要使用,需要在request函数中处理,不需要建议不要调用setCancel()方法。
cancel()
和setCancel()配套,如果设置了Abort controller,调用cancel()会取消当前请求,并将status设定为'canceled'。如果没有设置Abort controller,会在console中打印一个warning。
refetch()
如果设定了Abort controller,则取消当前fetch(),清除当前data,并使用原参数再次调用fetch()。如果没有设定Abort controller,则清除当前data,直接使用原参数再次调用fetch()。
刷新的快捷方法
option({ id, once = false }: { id?: string; once?: boolean })
设定改Request的token和是否只能被set()一次。token目前没有特殊用途,默认情况下,Request type可以被set()多次。