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