• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

在 React 通过 Suspense 实现以同步的方式获取数据

武飞扬头像
宗伦
帮助6

同步效果如下

建议大家阅读完之后再回来体验 学新通

什么是以同步的方式来获取数据

同步的方式(代码书写层面)来获取数据,其实也是践行代数效应(有兴趣的可以看看我的 践行代数效应的 hook 实现)的一种,在这种情况下,我们不必关心数据是怎么来的(远程获取或者本地获取),我们只需要调用获取数据的函数,然后根据返回值来使用就行了。

举个例子,正常在 React 中都是异步来获取数据,比如下面这样

function App() {
    const [state,setState] = useState(null);
    useEffect(()=>{
        getData().then(res =>{
            setState(res);
        })
    },[])
    
    return <div> {state} </div>
}

如果是同步来获取的话就是下面这个样

function App() {
   const state = getData(); 
    return <div> {state} </div>
}

是不是感觉代码简单了很多。当然现在这样写在正常的 js 中肯定是行不通的,但是未来还是很有可能实现的。虽然在 js 中不行,但是在 React 中还是可以落地的。

下面我们先过一下 React 文档:

React 官方文档

React.Suspense 可以指定加载指示器(loading indicator),以防其组件树中的某些子组件尚未具备渲染条件。在未来,我们计划让 Suspense 处理更多的场景,如数据获取等。你可以在 我们的路线图 了解这一点。

原文传送门

只有启用了 Suspense 的数据源才会激活 Suspense 组件,它们包括:

  • 使用支持 Suspense 的框架 Relay 和 Next.js
  • 使用 lazy 懒加载组件代码。

Suspense 无法 检测在 Effect 或事件处理程序中获取数据的情况。

在上面的 Albums 组件中,正确的数据加载方法取决于你使用的框架。如果你使用了支持 Suspense 的框架,你会在其数据获取文档中找到详细信息。

目前还不支持脱离框架使用支持 Suspense 的数据获取。实现支持 Suspense 的数据源的要求是不稳定的,也没有文档。用于将数据源与 Suspense 集成的官方 API 将在未来的 React 版本中发布。

原文传送门

文档总结

React 中的 Suspense 在未来会处理请求数据(也就能达到我们前面说的同步的效果),处理的数据源需要 Suspense 认识,React 目前还不支持(因为实现支持数据源的要求还没确定)也没有文档,但是已经有框架支持这么用——Relay 和 Next.js。

但是我有点懒,暂时不想去翻这俩框架,还是自己整一个吧

如何实现

关键点是要让 Suspense 认识,那我也不知道怎么才能让他认识,不过 Suspense 认识 Lazy 啊,那我们来借鉴下 Lazy 的实现不就行了。

学新通

实现看起来不难,实际上也不难。不过只有这个也不够,还有个问题, lazy 是把请求的 Promise 存储在 fiber 上的,那我们直接在函数组件里面用,函数组件每次都是重新执行啊,那我们要把 Promise 存在哪来保证每次拿到的都一样呢?

想到了几种方案:

  • 利用闭包
  • 采用类实现(使用起来不够优雅)
  • 直接通过变量来实现(太原始了)

最终选用了通过闭包来实现

const Uninitialized = -1;
const Pending = 0;
const Resolved = 1;
const Rejected = 2;

function syncRequest(api) {
    const payload = {
        _result: api,
        _status: Uninitialized
    };
    return function (query) {
        if (payload._status === Uninitialized) {
          const ctor = payload._result;
          const thenable = ctor(query);
          thenable.then(
            moduleObject => {
              if (payload._status === Pending || payload._status === Uninitialized) {
                const resolved  = payload;
                resolved._status = Resolved;
                resolved._result = moduleObject;
              }
            },
            error => {
              if (payload._status === Pending || payload._status === Uninitialized) {
                const rejected = payload;
                rejected._status = Rejected;
                rejected._result = error;
              }
            },
          );
          if (payload._status === Uninitialized) {
            const pending = payload;
            pending._status = Pending;
            pending._result = thenable;
          }
        }
        if (payload._status === Resolved) {
          const data = payload._result;
          return data;
        } else {
          throw payload._result;
        }
    };
}

// 使用

function getData(data) {
  return new Promise(function(resolve, reject) {
    setTimeout(()=>{
      resolve(data)
    },3000);
  })
}

const syncGetData = syncRequest(getData);


function Fn(props) {
    const data = syncGetData(123);
    return <div className="App">
      <p>{data}</p>
    </div>
  }
  
function App() {
  return (
  <Suspense fallback={ <div>加载中</div> }>
    <Fn />
 </Suspense>
  ) 
}

这样虽然实现了同步请求的功能,但是还是有点小问题,那就是不能更新数据。 所以呢核心需求就是需要更新,更新的花就只能通过 React 提供的东西来更新了,所以我们使用 自定义hook 来重新优化一下。

import { useState } from "react";

const Uninitialized = -1;
const Pending = 0;
const Resolved = 1;
const Rejected = 2;

function syncRequest(api) {
    const payload = {
        _result: api,
        _status: Uninitialized,
        _initApi: api,
        _query: undefined,
    };

    function read(query) {
      const [_, forceUpdate] = useState(1);
      payload.forceUpdate = forceUpdate;
      // query 是请求参数
      const queryData = payload._query || query;
      if (payload._status === Uninitialized) {
        const ctor = payload._result;
        const thenable = ctor(queryData);
        thenable.then(
          moduleObject => {
            if (payload._status === Pending || payload._status === Uninitialized) {
              const resolved  = payload;
              resolved._status = Resolved;
              resolved._result = moduleObject;
            }
          },
          error => {
            if (payload._status === Pending || payload._status === Uninitialized) {
              const rejected = payload;
              rejected._status = Rejected;
              rejected._result = error;
            }
          },
        );
        if (payload._status === Uninitialized) {
          const pending = payload;
          pending._status = Pending;
          pending._result = thenable;
        }
      }
      if (payload._status === Resolved) {
        const data = payload._result;
        return data;
      } else {
        throw payload._result;
      }
    };

    function update(query) {
       // query 是新的请求参数

       // 重置内部变量
      payload._result = payload._initApi;
      payload._status = Uninitialized;
      payload._query = query;
      payload.forceUpdate(num=>num 1);
    }
    return {
      read,
      update
    }
}

// 使用
function getData(data) {
  return new Promise(function(resolve, reject) {
    setTimeout(()=>{
      resolve(data)
    },3000);
  })
}

const syncGetData = syncRequest(getData);

function Fn() {
    const data = syncGetData.read(124);
    const upate = () => {
      syncGetData.update(data   1)
    }
    return <div className="App" onClick={upate}>
      更新{data}
    </div>
  }
  
function App() {
  return (
  <Suspense fallback={ <div>加载中</div> }>
    <Fn />
 </Suspense>
  ) 
}

最后

参考资料

React18.2.0源码

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhbjfc
系列文章
更多 icon
同类精品
更多 icon
继续加载