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 源码》