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

前言

成都创新互联公司专注于企业成都营销网站建设、网站重做改版、徐闻网站定制设计、自适应品牌网站建设、HTML5建站商城网站制作、集团公司官网建设、外贸网站建设、高端网站制作、响应式网页设计等建站业务,价格优惠性价比高,为徐闻等各大城市提供网站开发制作服务。

之前在学习 React Hooks 的过程中,看到一篇外网文章,通过 Hooks 来请求数据,并将这段逻辑抽象成一个新的 Hooks 给其他组件复用,我也在我的博客里翻译了一下:《在 React Hooks 中如何请求数据?》,感兴趣可以看看。虽然是去年的文章,在阅读之后一下子就掌握了 Hooks 的使用方式,而且数据请求是在业务代码中很常用的逻辑。

Vue 3 已经发布一段时间了,其组合 API 多少有点 React Hooks 的影子在里面,今天我也打算通过这种方式来学习下组合 API。

项目初始化

为了快速启动一个 Vue 3 项目,我们直接使用当下最热门的工具 Vite 来初始化项目。整个过程一气呵成,行云流水。

 
 
 
 
  1. npm init vite-app vue3-app
 
 
 
 
  1. # 打开生成的项目文件夹
  2. cd vue3-app
  3. # 安装依赖
  4. npm install
  5. # 启动项目
  6. npm run dev

我们打开 App.vue 将生成的代码先删掉。

组合 API 的入口

接下来我们将通过 Hacker News API 来获取一些热门文章,Hacker News API返回的数据结构如下:

 
 
 
 
  1. {
  2.   "hits": [
  3.     {
  4.       "objectID": "24518295",
  5.       "title": "Vue.js 3",
  6.       "url": "https://github.com/vuejs/vue-next/releases/tag/v3.0.0",
  7.     },
  8.     {...},
  9.     {...},
  10.   ]
  11. }

我们通过 ui > li 将新闻列表展示到界面上,新闻数据从 hits 遍历中获取。

 
 
 
 

在讲解数据请求前,我看先看看 setup() 方法,组合 API 需要通过 setup() 方法来启动,setup() 返回的数据可以在模板内使用,可以简单理解为 Vue 2 里面 data() 方法返回的数据,不同的是,返回的数据需要先经过 reactive() 方法进行包裹,将数据变成响应式。

组合 API 中请求数据

在 Vue 2 中,我们请求数据时,通常需要将发起请求的代码放到某个生命周期中(created或 mounted)。在 setup() 方法内,我们可以使用 Vue 3 提供的生命周期钩子将请求放到特定生命周期内,关于生命周期钩子方法与之前生命周期的对比如下:

生命周期

可以看到,基本上就是在之前的方法名前加上了一个 on,且并没有提供 onCreated 的钩子,因为在 setup() 内执行就相当于在 created 阶段执行。下面我们在 mounted 阶段来请求数据:

 
 
 
 
  1. import { reactive, onMounted } from 'vue'
  2. export default {
  3.   setup() {
  4.     const state = reactive({
  5.       hits: []
  6.     })
  7.     onMounted(async () => {
  8.       const data = await fetch(
  9.         'https://hn.algolia.com/api/v1/search?query=vue'
  10.       ).then(rsp => rsp.json())
  11.       state.hits = data.hits
  12.     })
  13.     return state
  14.   }
  15. }

最后效果如下:

Demo

监听数据变动

Hacker News 的查询接口有一个 query 参数,前面的案例中,我们将这个参数固定了,现在我们通过响应式的数据来定义这个变量。

 
 
 
 

现在我们在输入框修改,就能触发 state.query 同步更新,但是并不会触发 fetch 重新调用,所以我们需要通过 watchEffect() 来监听响应数据的变化。

 
 
 
 
  1. import { reactive, onMounted, watchEffect } from 'vue'
  2. export default {
  3.   setup() {
  4.     const state = reactive({
  5.       query: 'vue',
  6.       hits: []
  7.     })
  8.     const fetchData = async (query) => {
  9.       const data = await fetch(
  10.         `https://hn.algolia.com/api/v1/search?query=${query}`
  11.       ).then(rsp => rsp.json())
  12.       state.hits = data.hits
  13.     }
  14.     onMounted(() => {
  15.       fetchData(state.query)
  16.       watchEffect(() => {
  17.         fetchData(state.query)
  18.       })
  19.     })
  20.     return state
  21.   }
  22. }

由于 watchEffect() 首次调用的时候,其回调就会执行一次,造成初始化时会请求两次接口,所以我们需要把 onMounted 中的 fetchData 删掉。

 
 
 
 
  1. onMounted(() => {
  2. - fetchData(state.query)
  3.   watchEffect(() => {
  4.     fetchData(state.query)
  5.   })
  6. })

Demo

watchEffect() 会监听传入函数内所有的响应式数据,一旦其中的某个数据发生变化,函数就会重新执行。如果要取消监听,可以调用 watchEffect() 的返回值,它的返回值为一个函数。下面举个例子:

 
 
 
 
  1. const stop = watchEffect(() => {
  2.   if (state.query === 'vue3') {
  3.     // 当 query 为 vue3 时,停止监听
  4.     stop()
  5.   }
  6.   fetchData(state.query)
  7. })

当我们在输入框输入 "vue3" 后,就不会再发起请求了。

Demo

返回事件方法

现在有个问题就是 input 内的值每次修改都会触发一次请求,我们可以增加一个按钮,点击按钮后再触发 state.query 的更新。

 
 
 
 

可以注意到 button 绑定的 click 事件的方法,也是通过 setup() 方法返回的,我们可以将 setup() 方法返回值理解为 Vue2 中 data() 方法和 methods 对象的合并。

原先的返回值 state 变成了现在返回值的一个属性,所以我们在模板层取数据的时候,需要进行一些修改,在前面加上 state.。

 
 
 
 

Demo

返回数据

修改作为强迫症患者,在模板层通过 state.xxx 的方式获取数据实在是难受,那我们是不是可以通过对象解构的方式将 state 的数据返回呢?

 
 
 
 

答案是『不可以』。修改代码后,可以看到页面虽然发起了请求,但是页面并没有展示数据。

state 在解构后,数据就变成了静态数据,不能再被跟踪,返回值类似于:

 
 
 
 
  1. export default {
  2.   setup(props, ctx) {
  3.     // 省略部分代码...
  4.     return {
  5.       input: 'vue',
  6.       query: 'vue',
  7.       hits: [],
  8.       setQuery,
  9.     }
  10.   }
  11. }

Demo

为了跟踪基础类型的数据(即非对象数据),Vue3 也提出了解决方案:ref() 。

 
 
 
 
  1. import { ref } from 'vue'
  2. const count = ref(0)
  3. console.log(count.value) // 0
  4. count.value++
  5. console.log(count.value) // 1

上面为 Vue 3 的官方案例,ref() 方法返回的是一个对象,无论是修改还是获取,都需要取返回对象的 value 属性。

我们将 state 从响应对象改为一个普通对象,然后所有属性都使用 ref 包裹,这样修改后,后续的解构才做才能生效。这样的弊端就是,state 的每个属性在修改时,都必须取其value 属性。但是在模板中不需要追加 .value,Vue 3 内部有对其进行处理。

 
 
 
 
  1. import { ref, onMounted, watchEffect } from 'vue'
  2. export default {
  3.   setup() {
  4.     const state = {
  5.       input: ref('vue'),
  6.       query: ref('vue'),
  7.       hits: ref([])
  8.     }
  9.     const fetchData = async (query) => {
  10.       const data = await fetch(
  11.         `https://hn.algolia.com/api/v1/search?query=${query}`
  12.       ).then(rsp => rsp.json())
  13.       state.hits.value = data.hits
  14.     }
  15.     onMounted(() => {
  16.       watchEffect(() => {
  17.         fetchData(state.query.value)
  18.       })
  19.     })
  20.     const setQuery = () => {
  21.       state.query.value = state.input.value
  22.     }
  23.     return {
  24.       ...state,
  25.       setQuery,
  26.     }
  27.   }
  28. }

有没有办法保持 state 为响应对象,同时又支持其对象解构的呢?当然是有的,Vue 3 也提供了解决方案:toRefs() 。toRefs() 方法可以将一个响应对象变为普通对象,并且给每个属性加上 ref()。

 
 
 
 
  1. import { toRefs, reactive, onMounted, watchEffect } from 'vue'
  2. export default {
  3.   setup() {
  4.     const state = reactive({
  5.       input: 'vue',
  6.       query: 'vue',
  7.       hits: []
  8.     })
  9.     const fetchData = async (query) => {
  10.       const data = await fetch(
  11.         `https://hn.algolia.com/api/v1/search?query=${query}`
  12.       ).then(rsp => rsp.json())
  13.       state.hits = data.hits
  14.     }
  15.     onMounted(() => {
  16.       watchEffect(() => {
  17.         fetchData(state.query)
  18.       })
  19.     })
  20.     const setQuery = () => {
  21.       state.query = state.input
  22.     }
  23.     return {
  24.       ...toRefs(state),
  25.       setQuery,
  26.     }
  27.   }
  28. }

Loading 与 Error 状态

通常,我们发起请求的时候,需要为请求添加 Loading 和 Error 状态,我们只需要在 state中添加两个变量来控制这两种状态即可。

 
 
 
 
  1. export default {
  2.   setup() {
  3.     const state = reactive({
  4.       input: 'vue',
  5.       query: 'vue',
  6.       hits: [],
  7.       error: false,
  8.       loading: false,
  9.     })
  10.     const fetchData = async (query) => {
  11.       state.error = false
  12.       state.loading = true
  13.       try {
  14.         const data = await fetch(
  15.           `https://hn.algolia.com/api/v1/search?query=${query}`
  16.         ).then(rsp => rsp.json())
  17.         state.hits = data.hits
  18.       } catch {
  19.         state.error = true
  20.       }
  21.       state.loading = false
  22.     }
  23.     onMounted(() => {
  24.       watchEffect(() => {
  25.         fetchData(state.query)
  26.       })
  27.     })
  28.     const setQuery = () => {
  29.       state.query = state.input
  30.     }
  31.     return {
  32.       ...toRefs(state),
  33.       setQuery,
  34.     }
  35.   }
  36. }

同时在模板使用这两个变量:

 
 
 
 

展示 Loading、Error 状态:

Demo

将数据请求逻辑抽象

用过 umi 的同学肯定知道 umi 提供了一个叫做 useRequest 的 Hooks,用于请求数据非常的方便,那么我们通过 Vue 的组合 API 也可以抽象出一个类似于 useRequest 的公共方法。

接下来我们新建一个文件 useRequest.js :

 
 
 
 
  1. import {
  2.   toRefs,
  3.   reactive,
  4. } from 'vue'
  5. export default (options) => {
  6.   const { url } = options
  7.   const state = reactive({
  8.     data: {},
  9.     error: false,
  10.     loading: false,
  11.   })
  12.   const run = async () => {
  13.     state.error = false
  14.     state.loading = true
  15.     try {
  16.       const result = await fetch(url).then(res => res.json())
  17.       state.data = result
  18.     } catch(e) {
  19.       state.error = true
  20.     }
  21.     state.loading = false
  22.   }
  23.   return {
  24.     run,
  25.     ...toRefs(state)
  26.   }
  27. }

然后在 App.vue 中引入:

 
 
 
 

当前的 useRequest 还有两个缺陷:

1.传入的 url 是固定的,query 修改后,不能及时的反应到 url 上;

2.不能自动请求,需要手动调用一下 run 方法;

 
 
 
 
  1. import {
  2.   isRef,
  3.   toRefs,
  4.   reactive,
  5.   onMounted,
  6. } from 'vue'
  7. export default (options) => {
  8.   const { url, manual = false, params = {} } = options
  9.   const state = reactive({
  10.     data: {},
  11.     error: false,
  12.     loading: false,
  13.   })
  14.   const run = async () => {
  15.     // 拼接查询参数
  16.     let query = ''
  17.     Object.keys(params).forEach(key => {
  18.       const val = params[key]
  19.       // 如果去 ref 对象,需要取 .value 属性
  20.       const value = isRef(val) ? val.value : val
  21.       query += `${key}=${value}&`
  22.     })
  23.     state.error = false
  24.     state.loading = true
  25.     try {
  26.       const result = await fetch(`${url}?${query}`)
  27.        .then(res => res.json())
  28.       state.data = result
  29.     } catch(e) {
  30.       state.error = true
  31.     }
  32.     state.loading = false
  33.   }
  34.   onMounted(() => {
  35.     // 第一次是否需要手动调用
  36.     !manual && run()
  37.   })
  38.   return {
  39.     run,
  40.     ...toRefs(state)
  41.   }
  42. }

经过修改后,我们的逻辑就变得异常简单了。

 
 
 
 
  1. import useRequest from './useRequest'
  2. export default {
  3.   setup() {
  4.     const query = ref('vue')
  5.     const { data, loading, error, run } = useRequest(
  6.       {
  7.         url: 'https://hn.algolia.com/api/v1/search',
  8.         params: {
  9.           query
  10.         }
  11.       }
  12.     )
  13.     return {
  14.       data,
  15.       query,
  16.       error,
  17.       loading,
  18.       search: run,
  19.     }
  20.   }
  21. }

当然,这个 useRequest 还有很多可以完善的地方,例如:不支持 http 方法修改、不支持节流防抖、不支持超时时间等等。最后,希望大家看完文章后能有所收获。

本文转载自微信公众号「更了不起的前端」,可以通过以下二维码关注。转载本文请联系更了不起的前端公众号。

分享名称:Vue3的组合API如何请求数据?
文章来源:http://www.gawzjz.com/qtweb2/news17/28567.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联