同步效果如下
建议大家阅读完之后再回来体验
什么是以同步的方式来获取数据
同步的方式(代码书写层面)来获取数据,其实也是践行代数效应(有兴趣的可以看看我的 践行代数效应的 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>
)
}
最后
以上就是对在 React 中同步获取数据的简单实现。
感谢大家的阅读,有不对的地方也欢迎大家指出来。
参考资料
React18.2.0源码
原文链接:https://juejin.cn/post/7261554925867630649 作者:宗伦
code前端网

