RSM:超實用的多場景請求管理方案

愛程式設計的小金發表於2022-11-24

各位前端er們,用了那麼久的再熟悉不過的介面請求,有沒有覺得不妥???你可能會覺得,這有什麼不妥,不就axios.get一下,fetch一下,請求就發出了嘛,然後再處理返回的資料,完事。真有這麼簡單嗎?問題來了,針對不同場景下的請求需求,真的可以統一這樣處理就完事嗎?我們來聊聊請求這事兒!

聊聊前端請求的場景

我們先來聊聊前端請求的各種場景,以下是一些做請求時遇到的高頻場景。

  1. 什麼時候發出請求,是一進入頁面,還是點選某個按鈕再觸發,還是某個資料改變後就觸發,還是...?
  2. 是否要在頁面上展示請求狀態?
  3. 某個請求是否要封裝成一個函式,以便重複呼叫,比如翻頁請求;
  4. 使用者可能會高頻請求的介面是否做資料快取,比如在數秒內會重複檢視的資料?
  5. 處理完一些資料後需要跨頁面或模組運算元據,要怎麼做更方便?總不能都放在全域性狀態內吧;
  6. 離線了還能安全提交資料嗎,比如編輯器需要頻繁自動儲存草稿,突然離線了怎麼辦?
  7. 其他請求場景...

這些請求場景在不同專案中,或相同專案的不同的位置都需要做不同的處理,如果我們還只是單純的用請求庫發起請求,那就會有失效能和優雅度。

對請求場景進行管理

因為上面這些問題,就產生了一個概念,叫請求場景管理(RSM,Request scene management),下面我們來聊聊這個概念,先上圖,英文圖,自行翻譯。

image.png

和Flux概念一樣,它僅僅只是提出了一套流程規範,然後各開發者可以根據它進行參考改造變形一通形成自己獨有的思路,再然後編碼實現它。

我的理解是它更像是給請求庫安上了機械臂、噴漆桶什麼的,把請求庫武裝起來了,就像鋼鐵俠一樣。

123.png

在這裡,這個規範提出了4個流程,分別如下:

請求時機

描述在什麼時候需要發出請求。

  • 初始化展示資料,如剛進入某個介面或子介面;
  • 人機互動觸發CS互動,需要變更資料重新發出請求,如翻頁、篩選、排序、模糊搜尋等;
  • 預載入資料,如分頁內預先載入下一頁內容、預測使用者點選某個按鈕後預先拉取資料;
  • 操作服務端資料,需發出增刪改查請求,如提交資料、刪除資料等;
  • 同步服務端狀態,如資料變化較快的場景下輪詢請求、操作了某個資料後重新拉取資料;

請求行為

描述以怎樣的方式處理請求。

  • 佔位請求,請求時展示loading、骨架圖、或者是上次使用的真實資料;
  • 快取高頻響應,多次執行請求會使用保鮮資料;
  • 多請求序列與並行;
  • 對頻繁的請求進行防抖,避免前端資料閃動,以及降低服務端壓力;
  • 重要介面重試機制,降低網路不穩定造成的請求失敗機率;
  • 靜默提交,當只關心提交資料時,提交請求後直接響應成功事件,後臺保證請求成功;
  • 離線提交,離線時將提交資料暫存到本地,網路連線後再提交;

請求事件

表示攜帶請求引數傳送請求,獲得響應,這就是我們最常用到的axiosfetchXMLHttpRequest等,看!這裡我們要重新認識上面這些請求方案的定位了,它只是作為請求場景管理的環節之一。

響應資料管理

顧名思義,就是統一管理響應資料,任何位置都可以對響應資料進行操作,這部分基本是結合reactvue等MVVM框架的狀態機制來使用的,就好像有個專門用來管理響應資料的reduxvuex,你可以跨模組操作這些狀態化的響應資料,而不用什麼事件機制。

  • 移除快取響應資料,再次發起請求時將從服務端拉取;
  • 更新快取響應資料,可更新任意位置響應資料,非常有利於跨頁面更新資料;
  • 重新整理響應資料,可重新重新整理任意位置的響應資料,也非常有利於跨頁面更新資料;
  • 自定義設定快取,在請求批次資料時,可手動對批次資料一一設定快取,從而滿足後續單條資料的快取命中;

alova,一個RSM實現庫

alova是我發現的較為簡易上手的RSM實現庫,因為它的設計真的很像axios,新手也能很快上手,同時它也可以和任意的請求庫進行協作。

簡單演示下alova的使用方法,終於要開始實操上程式碼了,在使用alova之前需要先建立一個例項,假設我們在vue專案中使用。

import { createAlova } from 'alova';
import VueHook from 'alova/vue';
import GlobalFetch from 'alova';

const alovaInstance = createAlova({
    // 請求介面字首
    baseURL: 'https://api.alovajs.org',
    
    // 用於給mvvm庫建立狀態化響應資料
    // vue專案傳入VueHook,react專案傳入ReactHook
    statesHook: VueHook,
    
    // 傳一個請求介面卡,GlobalFetch是我們提供的fetch api介面卡
    // 你想用axios也可以自定義一個介面卡
    requestAdapter: GlobalFetch(),
    
    // 是不是有熟悉的味道
    beforeRequest(config) {
        config.headers.token = 'tokenxxx';
    },
    async responsed(response) {
      const json = await response.json();
      if (json.code !== 200) {
        throw new Error(json.message);
      }
      return json.data;
    },
});

以Todo為例,發起todo詳情請求

// 先定義一個請求函式,該函式返回的是一個請求物件,表示一次請求的資訊,但還不會實際發出請求
// 它的用法很接近axios
const getTodoDetail = id => alovaInstance.Get('/todo', {
    params: {
        id
    },
    
    // 本地快取50000毫秒,再次請求時將會命中快取,而不會再次發起請求
    localCache: 50000,
});

// 發起請求
const {
    // loading是載入狀態值,當載入時它的值為true,結束後自動更新為false
    // Vue3環境下,它是一個Ref型別的值,你可以透過loading.value訪問它,或直接繫結到介面中
    loading,
    

    // 狀態化的響應資料
    data: todoDetail,

    // 請求錯誤物件,請求錯誤時有值,否則為undefined
    error,

    // 成功回撥繫結
    onSuccess,

    // 失敗回撥繫結
    onError,

    // 完成回撥繫結
    onComplete,
    
} = useRequest(getTodoDetail(this.$params.todoId)); // 將請求物件傳入即可傳送請求
onSuccess(todoListRaw => {
  console.log('請求成功,這裡也可以訪問響應資料:', todoListRaw);
});
onError(error => {
  console.log('請求失敗,錯誤資訊為:', error);
});
onComplete(() => {
  console.log('請求完成,不管成功失敗都會呼叫');
});

你可以直接使用useRequest返回的狀態繫結到介面上

<div v-if="loading">Loading...</div>
<div v-else-if="error" class="error">{{ error.message }}</div>
<template v-else>
    <div class="todo-title">{{ todoDetail.title }}</div>
    <div class="todo-time">{{ todoDetail.time }}</div>
</template>

提交資料

// 建立提交資料的請求物件
const createTodo = newTodo => alovaInstance.Post('/create-todo', newTodo);

const {
    loading,
    data,
    error,

    // 手動傳送器請求的函式,呼叫後傳送請求
    send: addTodo,
} = useRequest(newTodo => createTodoPoster(newTodo), {
    // 當immediate為false時,預設不發出
    immediate: false
});

// 假設這是我們需要提交的資料
const newTodo = {
    title: '新的todo項',
    time: new Date().toLocaleString()
};

// 用於提交成功後重新整理todo列表資料
const { fetch } = useFetcher(alovaInstance);

// 手動傳送請求
const handleAddTodo = async () => {
    try {
        cosnt result = await addTodo(newTodo);
        console.log('新增todo項成功,響應資料為:', result);
        
        // 提交成功後可能需要重新整理todo列表資料,可以在這裡呼叫fetch函式
        fetch(getTodoList());
    } catch(error) {
        console.log('新增todo項失敗,錯誤資訊為:', error);
    }
};

在介面中點選按鈕發起請求

<!-- 忽略其他... -->
<button @click="handleAddTodo">建立todo</button>

總結

因為篇幅的原因,今天的分享就先到這啦,這裡也只是演示了一點點alova的功能,它的強大還不止於此,它也給了我們很多很實用的請求處理方案,不管是在專案中提升效能,提升程式碼優越度,亦或是降低服務端壓力,都有很大的幫助。有興趣的小夥伴可以去github上詳細瞭解瞭解!

各位,你們覺得還有哪些請求場景的例子嗎?

相關文章