1. 程式人生 > 其它 >handsontable vue 資料載入_【第七期】使用 vueasynmanager 管理 Vue 應用中的非同步呼叫...

handsontable vue 資料載入_【第七期】使用 vueasynmanager 管理 Vue 應用中的非同步呼叫...

技術標籤:handsontable vue 資料載入

不知道大家對 React Suspense 是否有過關注,也許 Suspense 讓人比較激動的是在服務端的流式渲染,然而從目前來看,React Suspense 的功能其實就是個 Loadable。當然啦這是我個人的看法,不過這不是今天的重點,今天的重點是介紹如何在 Vue 應用中更好的管理非同步呼叫,那為什麼會扯到 React Suspense 呢?因為 vue-async-manager 的靈感來自於 React Suspense,因此讓我們開始吧。

vue-async-manager 是一個開源專案:

•GitHub地址:https://github.com/shuidi-fed/vue-async-manager•Docs(中文|英文):https://shuidi-fed.github.io/vue-async-manager/•線上 Demos:https://shuidi-fed.github.io/vue-async-manager/demo.html

指南

在 Vue 應用中更輕鬆的管理非同步呼叫

非同步呼叫指的是什麼?

這裡所說的非同步呼叫,主要指的是兩件事兒:

•非同步元件(Async Component)的載入•傳送非同步請求從 API 中獲取資料

等待非同步元件的載入

實際上Vue的非同步元件已經支援在載入過程中展示loading元件的功能,如下程式碼取自官網:

new Vue({  // ...  components: {    'my-component': () => ({        // 非同步元件        component: import('./my-async-component'),        // 載入非同步元件過程中展示的 loading 元件        loading: LoadingComponent,        // loading 元件展示的延遲時間        delay: 200    })  }})

Tip:delay用於指定loading元件展示的延遲時間,如上程式碼中延遲時間為200ms,如果非同步元件的載入在200ms之內完成,則loading元件就沒有展示的機會。

但它存在兩個問題:

•1、loading元件與非同步元件緊密關聯,無法將loading元件提升,並用於多個非同步元件的載入。•2、如果非同步元件自身仍有非同步呼叫,例如請求 API,那麼loading元件是不會等待 API 請求完成之後才隱藏的。

vue-async-manager提供了元件,可以解決如上兩個問題。

1、使用 lazy 函式建立非同步元件

過去我們建立一個非同步元件的方式是:

const asyncComponent = () => import('./my-async.component.vue')

現在我們使用vue-async-manager提供的lazy函式來建立非同步元件:

import { lazy } from 'vue-async-manager'const asyncComponent = lazy(() => import('./my-async.component.vue'))

如上程式碼所示,僅僅是將原來的非同步工廠函式作為引數傳遞給lazy函式即可。

2、使用元件包裹非同步元件

<template>  <div id="app">        <Suspense>            <div slot="fallback">loadingdiv>            <asyncComponent1/>      <asyncComponent2/>    Suspense>  div>template><script>// 建立非同步元件const asyncComponent1 = lazy(() => import('./my-async.component1.vue'))const asyncComponent2 = lazy(() => import('./my-async.component2.vue'))export default {  name: 'App',  components: {    // 註冊元件    asyncComponent1,    asyncComponent2  }}script>

只有當全部載入完畢後,loading元件才會消失。

Tip: Live Demo:等待所有非同步元件載入完畢[1]

配合 vue-router 使用

我們在開發Vue應用時,最常使用非同步元件的方式是配合vue-router做程式碼拆分,例如:

const router = new VueRouter({  routes: [    {      path: '/',      component: () => import('./my-async-component.vue')    }  ]})

為了讓元件等待這個非同步元件的載入,我們可以使用lazy函式包裹這個非同步元件工廠函式:

const router = new VueRouter({  routes: [    {      path: '/',      component: lazy(() => import('./my-async-component.vue'))    }  ]})

最後我們只需要用元件包裹渲染出口()即可:

<Suspense :delay="200">  <div slot="fallback">loadingdiv>    <router-view/>Suspense>

Tip: Live Demo:配合 vue-router[2]

API請求中如何展示 loading

過去,大多是手動維護loading的展示,例如“開始請求”時展示loading,“請求結束”後隱藏loading。而且如果有多個請求併發時,你就得等待所有請求全部完成後再隱藏loading。總之你需要自己維護loading的狀態,無論這個狀態是儲存在元件內,還是store中。

現在來看看vue-async-manager是如何解決 API 請求過程中loading展示問題的,假設有如下程式碼:

<Suspense>  <div slot="fallback">loading...div>  <MyComponent/>Suspense>

元件內渲染了元件,該元件是一個普普通通的元件,在該元件內部,會發送 API 請求,如下程式碼所示:

<template>    <div>{{ res }}div>template><script>import { getAsyncData } from 'api'export default {  data: {    res: {}  },  async created() {    // 非同步請求資料    this.res = await getAsyncData(id)  }}script>

這是我們常見的程式碼,通常在created或者mounted鉤子中傳送非同步請求獲取資料,然而這樣的程式碼對於元件來說,它並不知道需要等待非同步資料獲取完成後再隱藏loading。為了解決這個問題,我們可以使用vue-async-manager提供的createResource函式建立一個資源管理器

<template>    <div>{{ $rm.$result }}div>template><script>import { getAsyncData } from 'api'import { createResource } from 'vue-async-manager'export default {  created() {    // 建立一個資源管理器    this.$rm = createResource((params) => getAsyncData(params))    // 讀取資料    this.$rm.read(params)  }}script>

createResource函式傳遞一個工廠函式,我們建立了一個資源管理器$rm,接著呼叫資源管理器的$rm.read()函式進行讀取資料。大家注意,上面的程式碼是以同步的方式來編寫的,並且元件能夠知道該元件正在進行非同步呼叫,因此元件將等待該非同步呼叫結束之後再隱藏loading

另外我們觀察如上程式碼中的模板部分,我們展示的資料是$rm.$result,實際上非同步資料獲取成功之後,得到的資料會儲存在資源管理器$rm.$result屬性上,需要注意的是,該屬性本身就是響應式的,因此你無需在元件的data中事先宣告。


Tip: Live Demo:Suspense 元件等待資源管理器獲取資料完成[3]

配合 vuex

配合vuex很簡單,只需要使用mapActionsactions對映為方法即可:

export default {  name: "AsyncComponent",  methods: {    ...mapActions(['increase'])  },  created() {    this.$rm = createResource(() => this.increase())    this.$rm.read()  }};

Tip: Live Demo:配合 vuex[4]

捕獲元件樹中的所有非同步呼叫

元件不僅能捕獲非同步元件的載入,如果該非同步元件自身還有其他的非同步呼叫,例如通過資源管理器獲取資料,那麼元件也能夠捕獲到這些非同步呼叫,並等待所有非同步呼叫結束之後才隱藏loading狀態。

我們來看一個例子:

<Suspense>  <div slot="fallback">loadingdiv>    <MyLazyComopnent/>Suspense>

在這段程式碼中,元件是一個通過lazy函式建立的元件,因此元件可以等待該非同步元件的載入,然而非同步元件自身又通過資源管理器獲取資料:

// 非同步元件export default {  created() {    // 建立一個資源管理器    this.$rm = createResource((params) => getAsyncData(params))    this.$rm.read(params)  }}

這時候,元件會等待兩個非同步呼叫全部結束之後才隱藏loading,這兩個非同步呼叫分別是:

•1、非同步元件的載入•2、非同步元件內部通過資源管理器發出的非同步請求

Tip: 這個 Demo 也展示瞭如上描述的功能:

Live Demo:Suspense 元件等待資源管理器獲取資料完成[5]

資源管理器

前面我們一直在強調一個詞:資源管理器,我們把通過createResource()函式建立的物件稱為資源管理器(Resource Manager),因此我們約定使用名稱$rm來儲存createResource()函式的返回值。

資源管理器的完整形態如下:

this.$rm = createResource(() => getAsyncData())this.$rm = {    read(){},   // 一個函式,呼叫該函式會真正傳送非同步請求獲取資料    $result,    // 初始值為 null,非同步資料請求成功後,儲存著取得的資料    $error,     // 初始值為 null,當非同步請求出錯時,其儲存著 err 資料    $loading,   // 一個boolean值,初始值為 false,代表著是否正在請求中    fork()      // 根據已有資源管理器 fork 一個新的資源管理器}

其中$rm.read()函式用來發送非同步請求獲取資料,可多次呼叫,例如點選按鈕再次呼叫其獲取資料。$rm.$result我們也已經見過了,用來儲存非同步獲取來的資料。$rm.$loading是一個布林值,代表著請求是否正在進行中,通常我們可以像如下這樣自定義loading展示:

<template>    <MyButton :loading="$rm.$loading" @click="submit" >提交MyButton>template><script>import { getAsyncData } from 'api'import { createResource } from 'vue-async-manager'export default {  created() {    // 建立一個資源管理器    this.$rm = createResource((id) => getAsyncData(id))  },  methods: {    submit() {      this.$rm.read(id)    }  }}script>

Tip: 更重要的一點是:資源管理器可以脫離單獨使用。

如果資源管理器在請求資料的過程中發生了錯誤,則錯誤資料會儲存在$rm.$error屬性中。$rm.fork()函式用來根據已有資源管理器建立一個一模一樣的資源管理器出來。

fork 一個資源管理器

當一個 API 用來獲取資料,並且我們需要併發的獲取兩次資料,那麼只需要呼叫兩次$rm.read()即可:

<script>import { getAsyncData } from 'api'import { createResource } from 'vue-async-manager'export default {  created() {    // 建立一個資源管理器    this.$rm = createResource((type) => getAsyncData(type))    // 連續獲取兩次資料    this.$rm.read('top')    this.$rm.read('bottom')  }}script>

但是這麼做會產生一個問題,由於一個資源管理器對應一個$rm.$result,它只維護一份請求回來的資料以及loading狀態,因此如上程式碼中,$rm.$result最終只會儲存$rm.read('bottom')的資料。當然了,有時候這是符合需求的,但如果需要儲存兩次呼叫的資料,那麼就需要fork出一個新的資源管理器:

<script>import { getAsyncData } from 'api'import { createResource } from 'vue-async-manager'export default {  created() {    // 建立一個資源管理器    this.$rm = createResource((type) => getAsyncData(type))    this.$rm2 = this.$rm.fork()    // 連續獲取兩次資料    this.$rm.read('top')    this.$rm2.read('bottom')  }}script>

這樣,由於$rm$rm2是兩個獨立的資源管理器,因此它們互不影響。

prevent 選項與防止重複提交

假設我們正在提交表單,如果使用者連續兩次點選按鈕,就會造成重複提交,如下例子:

<template>  <button @click="submit">提交button>template><script>import { getAsyncData } from 'api'import { createResource } from 'vue-async-manager'export default {  created() {    // 建立一個資源管理器    this.$rm = createResource((type) => getAsyncData(type))  },  methods: {    submit() {      this.$rm.read(data)    }  }}script>

實際上,我們可以在建立資源管理器的時候提供prevent選項,這樣創建出來的資源管理器將自動為我們防止重複提交:

<template>  <button @click="submit">提交button>template><script>import { getAsyncData } from 'api'import { createResource } from 'vue-async-manager'export default {  created() {    // 建立一個資源管理器    this.$rm = createResource((type) => getAsyncData(type), { prevent: true })  },  methods: {    submit() {      this.$rm.read(data)    }  }}script>

當第一次點選按鈕時會發送一個請求,在這個請求完成之前,將不會再次傳送下一次請求。直到上一次請求完成之後,$rm.read()函式才會再次傳送請求。

loading 的展示形態

loading的展示形態可以分為兩種:一種是隻展示loading,不展示其他內容;另一種是正常渲染其他內容的同時展示loading,比如頁面頂部有一個長長的載入條,這個載入條不影響其他內容的正常渲染。

因此vue-async-manager提供了兩種渲染模式:

import VueAsyncManager from 'vue-async-manager'Vue.use(VueAsyncManager, {  mode: 'visible' // 指定渲染模式,可選值為 'visible' | 'hidden',預設值為:'visible'})

預設情況下采用'visible'的渲染模式,意味著loading的展示可以與其他內容共存,如果你不想要這種渲染模式,你可以指定mode'hidden'

另外以上介紹的內容都是由元件來控制loading的展示,並且loading的內容由元件的fallback插槽決定。但有的時候我們希望更加靈活,我們經常遇到這樣的場景:點選按鈕的同時在按鈕上展示一個微小的loading狀態,我們的程式碼看上去可能是這樣的:

<MyButton :loading="isLoading" >提交MyButton>

loading的形態由元件提供,換句話說,我們拋棄了fallback插槽作為loading來展示。因此,我們需要一個手段來得知當前是否處於正在載入的狀態,在上面我們已經介紹了該問題的解決辦法,我們可以使用資源管理器的$rm.$loading屬性:

<MyButton :loading="$rm.$loading" >提交MyButton>

錯誤處理

lazy元件載入失敗會展示元件的error插槽,你也可以通過監聽rejected事件來自定義錯誤處理。

Tip: Live Demo:載入失敗展示 error 插槽[6]

當錯誤發生時除了展示error插槽,你還可以通過監聽元件的rejected事件來自定義處理:

<template>  <Suspense :delay="200" @rejected="handleError">    <p class="fallback" slot="fallback">loadingp>    <AsyncComponent/>  Suspense>template><script>export default {  // ......  methods: {    handleError() {      // Custom behavior    }  }};script>

Tip: Live Demo:通過事件處理 error[7]

關於 LRU 快取

React Cache使用LRU演算法快取資源,這要求 API 具有冪等性,然而在我的工作環境中,在給定時間週期內真正冪等的 API 很少,因此暫時沒有提供對快取資源的能力。

Demo References

[1]等待所有非同步元件載入完畢:https://shuidi-fed.github.io/vue-async-manager/zh/demo.html#%E7%AD%89%E5%BE%85%E6%89%80%E6%9C%89%E5%BC%82%E6%AD%A5%E7%BB%84%E4%BB%B6%E5%8A%A0%E8%BD%BD%E5%AE%8C%E6%AF%95[2]配合 vue-router:https://shuidi-fed.github.io/vue-async-manager/zh/demo.html#%E9%85%8D%E5%90%88-vue-router[3]Suspense 元件等待資源管理器獲取資料完成:https://shuidi-fed.github.io/vue-async-manager/zh/demo.html#suspense-%E7%BB%84%E4%BB%B6%E7%AD%89%E5%BE%85%E8%B5%84%E6%BA%90%E7%AE%A1%E7%90%86%E5%99%A8%E8%8E%B7%E5%8F%96%E6%95%B0%E6%8D%AE%E5%AE%8C%E6%88%90[4]配合 vuex:https://shuidi-fed.github.io/vue-async-manager/zh/demo.html#%E9%85%8D%E5%90%88-vuex[6]載入失敗展示 error 插槽:https://shuidi-fed.github.io/vue-async-manager/zh/demo.html#%E5%8A%A0%E8%BD%BD%E5%A4%B1%E8%B4%A5%E5%B1%95%E7%A4%BA-error-%E6%8F%92%E6%A7%BD[7]通過事件處理 error:https://shuidi-fed.github.io/vue-async-manager/zh/demo.html#%E9%80%9A%E8%BF%87%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86-error


水滴前端團隊招募夥伴,歡迎投遞簡歷到郵箱:[email protected]

bd9c491e37732fc2f4583583f1c9c4cf.png