React Suspense + 自定義Hook開啟數據請求新方式。

  • 2020 年 4 月 11 日
  • 筆記

過去

類組件

在React的類組件時代,請求數據的時機經常放在componentDidMount中,然後state中需要有一個變數記錄當前是否正在請求介面,在請求的前後需要手動去改變這些狀態,大概程式碼如下:

class App extends Component {    state = {      loading: false,    }      componentDidMount() {      this.setState({        data: null,        loading: true,      });      axios.get('/api/test').then((data) => {        this.setState({          data,          loading: false,        });      });    }      render() {      return this.state.loading ? '正在載入中...' : (        <Page data={data} />      );    }  }    複製程式碼

hook組件

自從React發布了Hook以來,這個組織程式碼邏輯的方式廣受歡迎,在Hook時代我們可以把請求前後的loading狀態變數在自定義hook中管理起來,程式碼示例:

const useRequest = (fn, dependencies = []) => {    const [data, setData] = useState(defaultValue);    const [loading, setLoading] = useState(false);      useEffect(() => {      setLoading(true);        fn()        .then(res => {          setData(res);        })        .finally(() => {          setLoading(false);        });    }, dependencies);      return { data, setData, loading };  };  複製程式碼
// App.js  function App() {    const { loading, data } = useRequest(() => axios.get('/api/test'));    return loading ? '正在載入中...' : (      <Page data={data} />    );  }    複製程式碼

未來

Suspense組件 + useSWR

React發布了Suspense以後,數據請求又有了新思路,我們可以在視圖容器的外層包裹一層Suspense,在內部通過向外throw Promise的方式告知Suspense我們的組件還沒有準備好,需要展示Loading狀態。

具體的程式碼可以看這裡:codesandbox.io/s/react-swr…

// Router.js  import React, { Suspense } from "react";  import { BrowserRouter as Router, Switch, Route } from "react-router-dom";  import { Spin } from "antd";  import Author from "./Pages/Author";  import Table from "./Pages/Table";  import Layout from "./Layout";    export default function App() {    return (      <Router>        <Layout>          <Suspense fallback={<Spin tip="正在拚命獲取數據,請稍後..." />}>            <Switch>              <Route exact path="/">                <Author />              </Route>              <Route exact path="/table">                <Table />              </Route>            </Switch>          </Suspense>        </Layout>      </Router>    );  }    複製程式碼
// pages/Author  import React from "react";  import useSWR from "../use-swr";    export default function Author() {    const { data } = useSWR("/api/user");      return (      <div>        <span>Hello {data.userName}</span>      </div>    );  }  複製程式碼
import useSWR from "swr";  import fetcher from "./fetcher";    export default url => {    return useSWR(url, fetcher, { suspense: true });  };  複製程式碼
// fetcher  const fetcher = url => {    let responseData;      switch (url) {      case "/api/user":        responseData = {          userName: "ssh"        };        break;      default:        break;    }      return new Promise(resolve => {      setTimeout(() => {        resolve(responseData);      }, 2000);    });  };    export default fetcher;    複製程式碼

其實這個Demo中就是使用了swr這個庫,對配置項進行了一個簡單的封裝,開啟了suspense模式

第二項參數所需要的fetcher就是自己定義的返回promise的邏輯。

在這種Suspense模式下,我們可以輕鬆的實現Loading狀態的管理,而且不需要在Page組件中再去關心和聲明載入中的組件。

關於swr這個庫的具體分析文章可以查看這篇:精讀《Hooks 取數 – swr 源碼》

這個Demo中在路由進入過後如果再次進入,數據會直接顯示之前請求過的,你會發現這非常像Vue中的keep-alive帶來的效果,這是因為swr這個庫在suspense模式下默認做了數據的快取,如果想要關掉它目前還沒在文檔中看到相應的配置。

自己實現簡易的useSWR

// use-my-swr  import { useState, useEffect } from "react";    export default (url, fetcher) => {    const [data, setData] = useState(null);    const [loading, setLoading] = useState(false);      useEffect(() => {      setLoading(true);      fetcher(url)        .then(result => {          setData(result);        })        .finally(() => {          setLoading(false);        });    }, [url, fetcher]);      if (loading) {      throw Promise.resolve(null);    } else {      return { data };    }  };    複製程式碼

其實和上面寫的useRequest相比,就是在loading的時候向外拋出一個promise,其他並沒有什麼改變。

使用:

import React from "react";  import useSWR from "../use-my-swr";  import fetcher from "../fetcher";    export default function Author() {    const { data } = useSWR("/api/user", fetcher);      return (      <div>        <span>Hello {data && data.userName}</span>      </div>    );  }  複製程式碼

小結

這篇文章只是在Suspense到來之前的一點開胃前菜,在React的發展長河中,開發者經歷了各種各樣的寫法,HOC、render-props、hook,其最終的目的其實還是讓我們寫的程式碼更加易於閱讀和維護,讓開發者越爽越好。

HookSuspense碰撞在一起,讓組件內部的邏輯和請求、等待內部的狀態徹底解耦開來了,相比以前的類組件,程式碼變的越來越精簡。

期待React團隊的進一步動作吧!

參考文章: React Concurrent 模式搶先預覽上篇: Suspense the world 精讀《Hooks 取數 – swr 源碼》