最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React Hooks 第二期:聊聊发请求这件事

    正文概述 掘金(rexhang_web)   2020-12-31   552

        发请求这件最简单的事情,只要接触web编程都会经常接触的一个环节,但是怎么做好请求一直都是个值得思考的问题,我都很难讲清楚为什么要使用 hooks。关于 React Hooks 也看了不少文章,但是始终没找到关于"最简单"的 CRUD 日常实践。所以这期我就抛砖迎玉,说说我目前的粗浅理解。

    问题

    假设有个简单的需求: NewsList页面需要从先后端接口获取数据然后在前端进行渲染和展示。拿到手我们应该怎么做,第一步先在这里先提出几个问题:

    1. 请求的方法写在哪里?请求来的数据放在哪里?
    2. 如果请求还未完成,组件已被 Unmount,有没有做特殊处理?
    3. 请求的参数来自哪里,参数变化会触发重新请求吗?调用写在哪里?
    4. 请求对应的几个状态: loading,refreshing,error 在组件上处理了么?
    5. 如果请求需要轮询,怎么做?有没有处理页面 visibilitychange 的情况?
    6. 如果需要在参数变化后重新请求,如果参数频繁更新,会出现竞态(旧的请求因为慢,晚于后发的请求 resolve)的问题吗?

    这些都是我觉得在 Class Component 里写“过程式”代码较难解决的问题。有的问题即使使用了 redux 依然没法很方便的处理。所以之前我很喜欢 Apollo 这种把异步数据放在 HOC 自动处理的解决方法。顺便说一下,Apollo 新版 API 推荐的 renderProps 明显更加难用了... 当然 hooks 的版本也已经 beta 了。但当把逻辑彻底放在 HOC 或者 renderProps 里时,如果一个组件依赖于多个请求并且它们之间又有纠缠时,不是特别方便处理。这是因为 HOC 或 renderProps 莫名的建立起了一个本不存在的层级和作用域关系。

    那么在 Hooks 里是怎么样的呢?

    useData ?

    function useData(dataLoader, params) {
      const [loading, setLoading] = useState(false);
      const [error, setError] = useState(undefined);
      const [data, setData] = useState(undefined);
    
      useEffect(() => {
        dataLoader().then((responseData) => {
          // ...
          setData(responseData);
        });
      }, [dataLoader, ...params]);
    
      return { data, loading, error };
    }
    
    function MovieList({ page, size }) {
      const {
        data: movieListData,
        loading,
        error
      } = useData(
        () => api.queryMovieList({ page, size }),
        [page, size]
      );
    
      // ...
    }
    

    第一个想到的是这样一个 Hook,类似于 useEffect,传入获取请求数据的 dataLoader,以及这个数据源依的请求参数 page 和 size。这样看似没问题,但实际上 useEffect 的dependencies [dataLoader, ...params] 无法满足 hook 要求的静态依赖,因为 useData 内部无法判断 ...params 里具体有哪些值。上一章我说 useCallback 一般用于性能,没有必要使用。这里就可以看出来其实说错了...

    function useData(dataLoader) {
      // ...
    
      useEffect(() => {
        dataLoader().then((responseData) => {
          // ...
          setData(responseData);
        });
      }, [dataLoader);
    
      // ...
    }
    
    
    function MovieList({ page, size }}) {
      const queryMovieList = useCallback((page, size) => api.queryMovieList({ page, size }), [page, size]);
    
      const { data: movieListData, loading, error } = useData(queryMovieList);
    
      // ...
    }
    

    这里我们利用 useCallback 将 queryMovieList 和它的参数 page,size 绑定起来。只有在 page 或 size 变化时,queryMovieList 才会更新,然后自动触发 useData 内部的请求逻辑。这样的写法,虽然在组件里多了一次 useCallback 的调用,但是更利于 hook 的静态分析。

    有了 useData 这样的封装,得益于 hooks 拥有的完整组件生命周期控制,我们可以很容易的将之前提到的组件卸载处理,请求竞态等等的问题,封装在 hooks 中。这里以竞态的问题为例,大致介绍一下可能的方法:

    function useData(dataLoader) {
      const currentDataLoader = useRef(null);
      // ...
    
      useEffect(() => {
        currentDataLoader.current = dataLoader;
        dataLoader().then((responseData) => {
          // ...
    
          // 如果有更新的请求,放弃之前的
          if (currentDataLoader.current !== dataLoader) {        return;
          }
    
          setData(responseData);
        });
      }, [dataLoader);
    
      // ...
    }
    

    只需要利用 ref 记录下最新的一个 dataLoader,那么在请求 resolve 时就可以判断出是否是过时的数据了。当然以上的代码只是一个示例,还有更多可以调整优化的地方。

    处理异步状态

        以上的 useData 只能解决关于数据获取的那一部分问题,为了处理我们得到的几个状态,不免需要写出这样的代码:

    function MovieList({ page, size }}) {
      const queryMovieList = useCallback((page, size) => api.queryMovieList({ page, size }), [page, size]);
    
      const { data: movieListData, loading, error } = useData(queryMovieList);
    
      if (loading && data == null) {
        return <Spin />
      }
    
      if (error) {
        return <Exception />
      }
    
      return (
        <Spin loading={loading}>
          {renderMovieList(movieListData)}
        </Spin>
      );
    
      function renderMovieList(movieList) {
        return movieList.map(item => <MovieItem key={item.id} data={item} />)
      }
    }
    

    久而久之我们会发现在所有使用这个 useData 的组件中,我们都避免不了手写这两个 if。但是 hooks 只能解决生命周期的问题,没法封装一些 render 的逻辑。其实这里最有效的解决方法就是 Suspense,但是因为还没有发布,所以我们想到了 renderProps:

    function DataBoundary({ data, loading, error, children }) {
      if (loading) {
        return <Spin />;
      }
    
      // ...
    
      return <Spin loading={loading}>{children(data)}</Spin>;
    }
    
    function MovieList({ page, size }}) {
      const queryMovieList = useCallback((page, size) => api.queryMovieList({ page, size }), [page, size]);
    
      const movieListResult = useData(queryMovieList);
      // const { data: movieListData, loading, error } = useData(queryMovieList);
    
      return (
        <DataBoundary {...movieListResult}>
        // <DataBoundary data={movieListData} loading={loading} error={error}>
          {(data) => renderMovieList(data)}
        </DataBoundary>
      );
    
      function renderMovieList(movieList) {
        return movieList.map(item => <MovieItem key={item.id} data={item} />)
      }
    }
    

    DataBoundary 里,我们封装了关于异步状态处理的渲染流程,还可以提供出类似 fallback 的钩子,满足需要定制化的组件场景。用它做到了类似于 Suspense 的事情。

    All in Hooks?

    但是如果我们真的想在 hooks 里完成所有的事情呢?这里再抛出一个彩蛋:

    function useDataBoundary(dataLoader) {
      // ...
    
      function boundary(renderChildren) {
        if (loading && data == null) {
          return <Spin />;
        }
    
        // ...
    
        return renderChildren(data);
      }
    
      return boundary;
    }
    
    function MovieList({ page, size }}) {
      const queryMovieList = useCallback((page, size) => api.queryMovieList({ page, size }), [page, size]);
    
      const boundary = useDataBoundary(queryMovieList);
    
      return (
        <div>
          {boundary((data) => renderMovieList(data))}
        </div>
      );
    
      function renderMovieList(movieList) {
        return movieList.map(item => <MovieItem key={item.id} data={item} />)
      }
    }
    

    如果我们可以在 hooks 中返回一个 "renderProp",我们就可以完整的将 render 相关的逻辑也封装在 hooks 里了,但是同时这样也会使得一个 hook 里的代码变得复杂,这里只是提供一种思路。那么这两种写法你更喜欢那个呢?

    总结

        这篇文章大致说明了在使用 hooks 写业务逻辑时的一点思考。很多地方没有详细展开,大家如果有兴趣的话可以再深入讨论。至少在最开始提出的几个问题上,hooks 帮我解决了这些原本没有地方安置的抽象逻辑。所以虽然我还是经常遇到 deps 没考虑清楚导致死循环的请求,我依然觉得 hooks 值得一试,文中提到的优化以及知识点只是冰山一角,还有更多实现方法,感兴趣可以看一下这篇文章《useRequest - 蚂蚁中台标准请求 Hooks》以及Umi Hooks。相信一定会受益匪浅。


    起源地下载网 » React Hooks 第二期:聊聊发请求这件事

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元