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,其最終的目的其實還是讓我們寫的程式碼更加易於閱讀和維護,讓開發者越爽越好。
Hook
和Suspense
碰撞在一起,讓組件內部的邏輯和請求、等待內部的狀態徹底解耦開來了,相比以前的類組件,程式碼變的越來越精簡。
期待React團隊的進一步動作吧!
參考文章: React Concurrent 模式搶先預覽上篇: Suspense the world 精讀《Hooks 取數 – swr 源碼》