前端請求的第N種方式——玩轉React Hook

我曾在幾年前寫過一篇文章——《 Jquery ajax, Axios, Fetch區別之我見》——從原理和使用層面分析了ajax,axios和fetch的區別。如今,本文從一個小的例子出發,經過使用react hook,給你們剖析一種新的數據請求方式;並經過這個自定義HOOK,引出&介紹其餘的React Hook庫。廢話很少說,咱們立刻開始。

1. 故事起源

最近在用umi寫一個項目,而後發現umi的網絡請求居然是這麼寫的:react

import { useRequest } from '@umijs/hooks';
import Mock from 'mockjs';
import React from 'react';

function getUsername(): Promise<string> {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(Mock.mock('@name'));
    }, 1000);
  });
}

export default () => {
  const { data, error, loading } = useRequest(getUsername)
  if (error) {
    return <div>failed to load</div>
  }
  if (loading) {
    return <div>loading...</div>
  }
  return <div>Username: {data}</div>
}

PS:試一下ios

在這個例子中,咱們使用useRequest對異步請求getUsername進行封裝,最終得到了一個對象,其中包含三個值:git

data: 正常請求返回的數據
error: 異常請求的錯誤信息
loading: 是否在加載/請求的狀態

這就讓咱們省卻了將新請求的數據設置爲組件的狀態等邏輯,讓整個代碼變得清晰明瞭了不少。而若是不使用useRequest,咱們代碼應該是下面這樣:github

import { useRequest } from "@umijs/hooks";
import Mock from "mockjs";
import React from "react";

const { useState, useEffect } = React;

function getUsername(): Promise<string> {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(Mock.mock("@name"));
    }, 1000);
  });
}

export default () => {
  const [username, setUsername] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  // 異步請求仍是要封裝一下
  async function getUsernameAsync() {
    setLoading(true);
    const res = await getUsername();
    // 在本例子中,getUsername永遠返回的都是請求正確的值,咱們這邊假設當請求失敗時,它會返回一個error對象
    if (typeof res === "string") {
      setUsername(res);
    } else {
      setError(res);
    }
    setLoading(false);
  }
  // 發起請求
  useEffect(() => {
    getUsernameAsync();
  }, []);

  if (error) {
    return <div>failed to load</div>;
  }
  if (loading) {
    return <div>loading...</div>;
  }
  return <div>Username: {username}</div>;
};

這下就應該很明確了,useRequest幫咱們抹平了三個state值,讓咱們能夠直接去使用它們。這就有點神奇了,它是怎麼實現的呢?是否還有什麼其餘的能力?立刻進入下一趴!ajax

2. 實現原理

在本節,咱們經過對react-use的useAsync進行代碼分析,來解釋上一節中爲何@umijs/hooks的useRequest能夠那麼作axios

useAsync代碼相對簡單,useRequest的代碼中摻雜了不少它的其餘特性,本質上它們的原理是一致的。
react-use: [項目地址]一個react hook函數庫,至關於react hook的JQuery,提供多種基本的基礎Hook封裝能力:如發送請求,獲取/設置cookie,獲取鼠標的位置等等。

PS:react-use的中文翻譯版本仍是針對V8.1.3的,而如今的版本是V15.1.1,因此能夠直接看英文文檔。segmentfault

首先仍是先貼一下使用方式:api

import {useAsync} from 'react-use';

function getUsername(): Promise<string> {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(Mock.mock("@name"));
    }, 1000);
  });
}

const Demo = () => {
  const state = useAsync(getUsername);

  return (
    <div>
      {state.loading?
        <div>Loading...</div>
        : state.error?
        <div>Error...</div>
        : <div>Value: {state.value}</div>
      }
    </div>
  );
};

能夠看到,使用方法都是同樣的,那麼下面咱們來看下它們是如何實現的——由於useAsync引用了自身的useAsyncFn,因此咱們下面直接來分析useAsyncFn。數組

/* eslint-disable */
// import暫時都不關心
import { DependencyList, useCallback, useState, useRef } from 'react';
import useMountedState from './useMountedState';
import { FnReturningPromise, PromiseType } from './util';
// AsyncState有四種狀態
export type AsyncState<T> =
  | {
      loading: boolean;
      error?: undefined;
      value?: undefined;
    }
  | {
      loading: true;
      error?: Error | undefined;
      value?: T;
    }
  | {
      loading: false;
      error: Error;
      value?: undefined;
    }
  | {
      loading: false;
      error?: undefined;
      value: T;
    };
// ts的類型暫時也都不關心
type StateFromFnReturningPromise<T extends FnReturningPromise> = AsyncState<PromiseType<ReturnType<T>>>;
export type AsyncFnReturn<T extends FnReturningPromise = FnReturningPromise> = [StateFromFnReturningPromise<T>, T];
// 正文,接受三個參數,一個異步請求函數,一個數組類型的依賴,一個初始狀態,默認是loading=false
export default function useAsyncFn<T extends FnReturningPromise>(
  fn: T,
  deps: DependencyList = [],
  initialState: StateFromFnReturningPromise<T> = { loading: false }
): AsyncFnReturn<T> {
  // 記錄是第幾回調用
  const lastCallId = useRef(0);
  // 判斷當前組件是否mounted完成
  const isMounted = useMountedState();
  // 設置狀態
  const [state, set] = useState<StateFromFnReturningPromise<T>>(initialState);
  // useCallback就是用來性能優化的,保證返回同一個回調函數,所以,咱們直接看它內部的回調函數就行
  const callback = useCallback((...args: Parameters<T>): ReturnType<T> => {
    // 第幾回調用
    const callId = ++lastCallId.current;
    // 直接設置爲loading爲true
    set(prevState => ({ ...prevState, loading: true }));
    // 當異步請求結束時,設置狀態
    return fn(...args).then(
      value => {
        // 當用戶屢次請求時,只返回最後一次請求的值
        isMounted() && callId === lastCallId.current && set({ value, loading: false });

        return value;
      },
      error => {
        isMounted() && callId === lastCallId.current && set({ error, loading: false });

        return error;
      }
    ) as ReturnType<T>;
  }, deps);
  // 返回兩個值,一個是state,一個是封裝好的callback
  return [state, (callback as unknown) as T];
}

這就是useAsyncFn的實現邏輯,能夠看到本質上和咱們第一節上的瘋狂設置狀態原理也差很少,可是這樣對請求的封裝卻打開了新的大門,具體有哪些能力呢?緩存

3. SWR和useRequest介紹

毫無疑問,第一節和第二節對異步請求的封裝避免了咱們在不一樣的請求中反覆的處理狀態,是能大幅提效的。那是否還有更牛逼的能力呢?它們來了!

SWR: [項目地址]一個React hook的異步請求庫。提供了異步請求的多種能力,例如:swr( stale-while-revalidate,先返回以前請求的緩存數據,再從新請求並刷新)能力,分頁,屏幕聚焦發送請求等。useRequest也是借鑑了SWR的思路。
useRequest: [項目地址]螞蟻中臺標準請求Hook倉庫。提供了多種能力,並被內置到umi中。

image.png

具體的能力你們能夠看《useRequest- 螞蟻中臺標準請求 Hooks》,已經很詳盡了,我就不過多介紹了。

4. 總結

本文從一個umi request的使用case入手,分析了它的原理。並在這個過程當中,分別介紹了react-use這個通用基礎hook倉庫,和SWR/useRequest這兩個異步請求hook倉庫。經過使用它們的能力,能夠大幅提高咱們的代碼編寫效率。

若是你尚未使用react hook的話,請立刻加入到react hook的你們庭中,由於隨着它的發展,它真的擁有了不少class component所不具有的特性。

相關文章
相關標籤/搜索