使用 react 的 hook 實現一個 useRequest

咱們在使用 react 開發前端應用時,不可避免地要進行數據請求,而在數據請求的先後都要執行不少的處理,例如javascript

  1. 展現 loading 效果告訴用戶後臺正在處理請求;
  2. 若要寫 async-await,每次都要 try-catch 進行包裹;
  3. 接口異常時要進行錯誤處理;

當請求比較多時,每次都要重複這樣的操做。這裏咱們能夠利用 react 提供的 hook,本身來封裝一個useRequesthtml

umi 框架中已經有實現了一個 useRequest 方法,useRequest-umi-hook,他這裏實現的功能不少,咱們只實現一個基本的功能,其餘更多的功能,您能夠本身拓展。前端

平平淡淡纔是真

1. 搭建一個基本結構

在 useRequest 中,咱們基於 axios 來封裝,不過這裏咱們不會對外暴露太多的配置,首先咱們來明確 useRequest 的輸入和輸出。java

要輸入的數據:react

  • url:要請求的接口地址;
  • data:請求的數據(默認只有 get 方式);
  • config:其餘一些配置,可選,如(manual?: boolean; // 是否須要手動觸發);

要返回的數據:ios

  • loading:數據是否在請求中;
  • data:接口返回的數據;
  • error:接口異常時的錯誤;

2. 具體的實現

咱們在肯定輸入和輸出的格式後,就能夠具體來實現了。json

2.1 不帶 config 配置的

首先定義好輸入和輸出的數據:axios

const useRequest = (url, data, config) => {
  const [loading, setLoading] = useState(false);
  const [result, setResult] = useState(null);
  const [error, setError] = useState(null);

  return {
    loading,
    result,
    error,
  };
};
export default useRequest;
複製代碼

而後就能夠添加數據請求了:markdown

const useRequest = (url, data, config) => {
  const [loading, setLoading] = useState(true);
  const [result, setResult] = useState(null);
  const [error, setError] = useState(null);

  const request = async () => {
    setLoading(true);
    try {
      const result = await axios({
        url,
        params: data,
        method: 'get',
      });
      if (result && result.status >= 200 && result.status <= 304) {
        setResult(result.data);
      } else {
        setError(new Error('get data error in useRequest'));
      }
    } catch (reason) {
      setError(reason);
    }
    setLoading(false);
  };
  useEffect(() => {
    request();
  }, []);

  return {
    loading,
    result,
    error,
  };
};
複製代碼

在咱們不考慮第三個配置 config 的狀況下,這個useRequest就已經可使用了。框架

const App = () => {
  const { loading, result, error } = useRequest(url);

  return (
    <div> <p>loading: {loading}</p> <p>{JSON.stringify(result)}</p> </div>
  );
};
複製代碼

強顏歡笑

2.2 添加取消請求

咱們在請求接口過程當中,可能接口還沒沒有返回到數據,組件就已經被銷燬了,所以咱們還要添加上取消請求的操做,避免操做已經不存在的組件。關於 axios 如何取消請求,您能夠查看以前寫的一篇文章:axios 源碼系列之如何取消請求

const request = useCallback(() => {
  setLoading(true);
  const CancelToken = axios.CancelToken;
  const source = CancelToken.source();

  axios({
    url,
    params: data,
    method: 'get',
    cancelToken: source.token, // 將token注入到請求中
  })
    .then((result) => {
      setResult(result.data);
      setLoading(false);
    })
    .catch((thrown) => {
      // 只有在非取消的請求時,才調用setError和setLoading
      // 不然會報組件已被卸載還會調用方法的錯誤
      if (!axios.isCancel(thrown)) {
        setError(thrown);
        setLoading(false);
      }
    });

  return source;
}, [url, data]);

useEffect(() => {
  const source = request();
  return () => source.cancel('Operation canceled by the user.');
}, [request]);
複製代碼

2.3 帶有 config 配置的

而後,接着咱們就要考慮把配置加上了,在上面的調用中,只要調用了 useRequest,就會立刻觸發。但若在符合某種狀況下才觸發怎辦呢(例如用戶點擊後才產生接口請求)? 這裏咱們就須要再加上一個配置。

{
  "manual": false, // 是否須要手動觸發,若爲false則馬上產生請求,若爲true
  "ready": false // 當manual爲true時生效,爲true時才產生請求
}
複製代碼

這裏咱們就要考慮 useRequest 中觸發 reqeust 請求的條件了:

  • 沒有 config 配置,或者有 config,但 manual 爲 false;
  • 有 config 配置,且 manual 和 ready 均爲 true;
const useRequest = () => {
  // 其餘代碼保持不變,並暫時忽略
  useEffect(() => {
    if (!config || !config.manual || (config.manual && config.ready)) {
      const source = request();
      return () => source.cancel('Operation canceled by the user.');
    }
  }, [config]);
};
複製代碼

使用方式:

const App = () => {
  const [ready, setReady] = useState(false);
  const { loading, result, error } = useRequest(url, null, { manual: true, ready });

  return (
    <div> <p>loading: {loading}</p> <p>{JSON.stringify(result)}</p> <button onClick={() => setReady(true)}>產生請求</button> </div>
  );
};
複製代碼

固然,實現手動觸發的方式有不少,我在這裏是用一個config.ready來觸發,在 umi 框架中,是對外返回了一個 run 函數,而後執行 run 函數再來觸發這個 useRequest 的執行。

const useRequest = () => {
  // 其餘代碼保持不變,並暫時忽略
  const [ready, setReady] = useState(false);

  const run = (r: boolean) => {
    setReady(r);
  };

  useEffect(() => {
    if (!config || !config.manual || (config.manual && ready)) {
      if (loading) {
        return;
      }
      setLoading(true);
      const source = request();

      return () => {
        setLoading(false);
        setReady(false);
        source.cancel('Operation canceled by the user.');
      };
    }
  }, [config, ready]);
};
複製代碼

3. 總結

咱們在這裏實現的useRequest,也只是實現幾個基本功能!對於更大的業務來講,可能需求點更多,例如輪訓,防抖等,這些功能就須要本身去添加啦!

相關文章
相關標籤/搜索