当前位置: 首页 > 科技观察

Vue3的组合API是如何请求数据的?_0

时间:2023-03-12 09:15:55 科技观察

前言在学习ReactHooks的过程中,在网上看到一篇文章,通过Hooks请求数据,并将这个逻辑抽象成一个新的Hooks,供其他组件复用。我也写在我的博客上我翻译在这里:《在 React Hooks 中如何请求数据?》,有兴趣的可以看看。虽然是去年的文章,但是看完之后立马掌握了Hooks的使用方法,数据请求是业务代码中很常见的逻辑。Vue3已经发布有一段时间了,它的组合API多少有点ReactHooks的影子。今天也打算通过这种方式来学习组合API。项目初始化为了快速启动一个Vue3项目,我们直接使用最流行的工具Vite来初始化项目。整个过程一气呵成,行云流水。npminitvite-appvue3-app#打开生成的项目文件夹cdvue3-app#安装依赖npminstall#启动项目npmrundev我们打开App.vue,先删除生成的代码。PortfolioAPI入口接下来,我们将使用HackerNewsAPI获取一些热门文章。HackerNewsAPI返回的数据结构如下:{"hits":[{"objectID":"24518295","title":"Vue.js3","url":"https://github.com/vuejs/vue-next/releases/tag/v3.0.0",},{...},{...},]}我们通过ui>li在界面上显示新闻列表,新闻数据为通过hits遍历得到在解释之前数据请求,让我看一下setup()方法。组合API需要通过setup()方法启动。setup()返回的数据可以在模板中使用,可以很容易理解Vue2中data()方法返回的数据,不同的是返回的数据需要通过react()方法进行包装首先使数据响应。在复合API中请求数据在Vue2中,当我们请求数据时,通常需要将发起请求的代码放入某个生命周期(created或mounted)。在setup()方法中,我们可以使用Vue3提供的生命周期钩子,将请求置于特定的生命周期中。lifecyclehook方法和之前的lifecycle对比如下:lifecycle可以看出来,基本上是在方法名前加上onon,并没有提供onCreatedhook,因为在setup()中执行的是相当于在创建阶段执行。接下来,我们在挂载阶段请求数据:https://hn.algolia.com/api/v1/search?query=vue').then(rsp=>rsp.json())state.hits=data.hits})returnstate}}最终效果如下如下:monitordataChangeHackerNews的查询接口有一个查询参数。在前面的例子中,我们固定了这个参数。现在我们通过响应式数据来定义这个变量。现在我们可以修改输入框来触发state.query同步更新,但是不会触发fetch再次调用,所以我们需要通过watchEffect()来监听响应数据的变化。import{reactive,onMounted,watchEffect}from'vue'exportdefault{setup(){conststate=reactive({query:'vue',hits:[]})constfetchData=async(query)=>{constdata=awaitfetch(`https://hn.algolia.com/api/v1/search?query=${query}`).then(rsp=>rsp.json())state.hits=data.hits}onMounted(()=>{fetchData(state.query)watchEffect(()=>{fetchData(state.query)})})returnstate}}第一次调用watchEffect()时,会执行一次它的回调,导致请求接口初始化时两次,所以我们需要删除onMounted中的fetchData。onMounted(()=>{-fetchData(state.query)watchEffect(()=>{fetchData(state.query)})})watchEffect()会监听传入函数中的所有响应数据,一旦其中一个If任何数据更改,该功能将重新执行。如果想取消手表,可以调用watchEffect()的返回值,返回一个函数。下面是一个例子:conststop=watchEffect(()=>{if(state.query==='vue3'){//当query是vue3时,停止监听stop()}fetchData(state.query)})whenAfter我们在输入框输入“vue3”,就不会再发起请求了。return事件方法的问题在于,每次修改输入中的值时,都会触发请求。我们可以添加一个按钮,点击按钮触发state.query的更新。你可以注意到绑定到按钮的点击事件方法,它也由setup()方法返回。我们可以把setup()方法的返回值理解为Vue2中data()方法和methods对象的组合。原来的返回值状态变成了当前返回值的一个属性,所以我们在模板层取数据的时候,需要做一些修改,加上状态。在前。返回数据修改为强迫症患者,在template层通过state.xxx获取数据真的很不爽,那我们能不能通过对象解构来返回state数据呢?;答案是不”。修改代码后可以看到,虽然页面发起了请求,但是页面并没有显示数据。状态被解构后,数据就变成了静态数据,无法再被追踪。返回值类似:exportdefault{setup(props,ctx){//省略部分代码...return{input:'vue',query:'vue',hits:[],setQuery,}}}为了追踪数据的基本类型(即非对象数据),Vue3也提出了一个解决方案:ref()。import{ref}from'vue'constcount=ref(0)console.log(count.value)//0count.value++console.log(count.value)//上面1是Vue3的官方案例,ref()方法返回一个对象,无论是修改还是获取,都需要获取返回对象的value属性。我们把state从一个response对象变成一个普通对象,然后把所有的属性都用ref包裹起来,这样修改之后,后续的解构才能生效。这样做的缺点是当修改状态的每个属性时,必须取其值属性。但是不需要在模板中附加.value,Vue3会在内部处理它。import{ref,onMounted,watchEffect}from'vue'exportdefault{setup(){conststate={input:ref('vue'),query:ref('vue'),hits:ref([])}constfetchData=async(查询)=>{constdata=awaitfetch(`https://hn.algolia.com/api/v1/search?query=${query}`).then(rsp=>rsp.json())state.hits.value=data.hits}onMounted(()=>{watchEffect(()=>{fetchData(state.query.value)})})constsetQuery=()=>{state.query.value=state.input。value}return{...state,setQuery,}}}有没有办法保持状态为响应对象,同时支持其对象解构?当然有,Vue3也提供了解决方案:toRefs()。toRefs()方法可以将一个响应对象变成一个普通对象,并将ref()添加到每个属性。import{toRefs,reactive,onMounted,watchEffect}from'vue'exportdefault{setup(){conststate=reactive({input:'vue',query:'vue',hits:[]})constfetchData=async(query)=>{constdata=awaitfetch(`https://hn.algolia.com/api/v1/search?query=${query}`).then(rsp=>rsp.json())state.hits=data.hits}onMounted(()=>{watchEffect(()=>{fetchData(state.query)})})constsetQuery=()=>{state.query=state.input}return{...toRefs(state),setQuery,}}}Loading和Error状态通常我们在发起请求的时候,需要给请求添加Loading和Error状态,我们只需要在state中添加两个变量来控制这两个状态。exportdefault{setup(){conststate=reactive({input:'vue',query:'vue',hits:[],error:false,loading:false,})constfetchData=async(query)=>{state.error=falsestate.loading=truetry{constdata=awaitfetch(`https://hn.algolia.com/api/v1/search?query=${query}`).then(rsp=>rsp.json())状态。hits=data.hits}catch{state.error=true}state.loading=false}onMounted(()=>{watchEffect(()=>{fetchData(state.query)})})constsetQuery=()=>{state.query=state.input}return{...toRefs(state),setQuery,}}}在模板中同时使用这两个变量:显示加载、错误状态State:抽象数据请求逻辑用过umi的同学一定知道umi提供了一个Hooks叫useRequest,非常方便请求数据,所以我们也可以通过Vue的组合API抽象一个类似useRequest的公共方法接下来我们创建一个新文件useRequest.js:import{toRefs,reactive,}from'vue'exportdefault(options)=>{const{url}=optionsconststate=reactive({data:{},error:false,loading:false,})construn=async()=>{state.error=falsestate.loading=truetry{constresult=awaitfetch(url).then(res=>res.json())state.data=result}catch(e){state.error=true}state.loading=false}return{run,...toRefs(state)}}然后在App.vue中引入:目前的useRequest还有两个缺陷:传入的url是固定的,query修改后不能及时响应url;不能自动请求,需要手动调用run方法;import{isRef,toRefs,reactive,onMounted,}from'vue'exportdefault(options)=>{const{url,manual=false,params={}}=optionsconststate=reactive({data:{},error:false,loading:false,})construn=async()=>{//拼接查询参数letquery=''Object.keys(params).forEach(key=>{constval=params[key]//如果去ref对象,需要取.value属性constvalue=isRef(val)?val.value:valquery+=`${key}=${value}&`})state.error=falsestate.loading=truetry{constresult=awaitfetch(`${url}?${query}`).then(res=>res.json())state.data=result}catch(e){state.error=true}state.loading=false}onMounted(()=>{//第一次需要手动调用吗!manual&&run()})return{run,...toRefs(state)}}修改后,我们的逻辑变得异常简单importuseRequestfrom'./useRequest'exportdefault{setup(){constquery=ref('vue')const{data,loading,error,run}=useRequest({url:'https://hn.algolia.com/api/v1/search',params:{query}})return{data,query,error,loading,search:run,}}}当然这个useRequest还有很大的改进空间,比如:不支持http方式修改,不支持Throttling防抖,不支持超时等。最后希望大家看完文章有所收获。