文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

第一个可以在条件语句中使用的原生Hook诞生了

2024-12-01 13:07

关注

大家好,我卡颂。

在10月13日的first-class-support-for-promises RFC[1]中,介绍了一种新的hook​ —— use。

use​什么?就是use​,这个hook​就叫use。这也是第一个:

本文来聊聊这个特殊的hook。

use是什么

我们知道,async函数会配合await关键词使用,比如:

async function load() {
const {name} = await fetchName();
return name;
}

类似的,在React组件中,可以配合use起到类似的效果,比如:

function Cpn() {
const {name} = use(fetchName());
return <p>{name}p>;
}

可以认为,use的作用类似于:

use​作为「读取异步数据的原语」,可以配合Suspense实现「数据请求、加载、返回」的逻辑。

举个例子,下述例子中,当fetchNote​执行异步请求时,会由包裹Note​的Suspense组件渲染「加载中状态」。

当请求成功时,会重新渲染,此时note数据会正常返回。

当请求失败时,会由包裹Note的ErrorBoundary组件处理失败逻辑。

function Note({id}) {
const note = use(fetchNote(id));
return (
<div>
<h1>{note.title}h1>
<section>{note.body}section>
div>
);
}

其背后的实现原理并不复杂:

  1. 当Note组件首次render,fetchNote发起请求,会throw promise,打断render流程。
  2. 以Suspense fallback作为渲染结果。
  3. 当promise状态变化后重新触发渲染。
  4. 根据note的返回值渲染。

实际上这套「基于promise的打断、重新渲染流程」当前已经存在了。use的存在就是为了替换上述流程。

与当前React​中已经存在的上述「promise流程」不同,use​仅仅是个「原语」(primitives),并不是完整的处理流程。

比如,use并没有「缓存promise」的能力。

举个例子,在下面代码中fetchTodo​执行后会返回一个promise,use​会消费这个promise。

async function fetchTodo(id) {
const data = await fetchDataFromCache(`/api/todos/${id}`);
return {contents: data.contents};
}
function Todo({id, isSelected}) {
const todo = use(fetchTodo(id));
return (
<div className={isSelected ? 'selected-todo' : 'normal-todo'}>
{todo.contents}
div>
);
}

当Todo组件的id prop变化后,触发fetchTodo重新请求是符合逻辑的。

但是当isSelected prop变化后,Todo组件也会重新render,fetchTodo执行后会返回一个新的promise。

返回新的promise不一定产生新的请求(取决于fetchTodo的实现),但一定会影响React接下来的运行流程(比如不能命中性能优化)。

这时候,需要配合React提供的cache API(同样处于RFC)。

下述代码中,如果id prop不变,fetchTodo始终返回同一个promise:

const fetchTodo = cache(async (id) => {
const data = await fetchDataFromCache(`/api/todos/${id}`);
return {contents: data.contents};
});

use的潜在作用

当前,use的应用场景局限在「包裹promise」。

但是未来,use会作为客户端中处理异步数据的主要手段,比如:

use(Context)​能达到与useContext(Context)​一样的效果,区别在于前者可以在条件语句,以及其他hook回调内执行。

可以利用use实现新的原生状态管理方案:

const currentState = use(store);
const latestValue = use(observable);

为什么不使用async await

本文开篇提到,use原语类似async await中的await,那为什么不直接使用async await呢?类似下面这样:

// Note 是 React 组件
async function Note({id, isEditing}) {
const note = await db.posts.get(id);
return (
<div>
<h1>{note.title}h1>
<section>{note.body}section>
{isEditing ? <NoteEditor note={note} /> : null}
div>
);
}

有两方面原因。

一方面,async await的工作方式与React客户端处理异步时的逻辑不太一样。

当await的请求resolve后,调用栈是从await语句继续执行的(generator中yield也是这样)。

而在React中,更新流程是从根组件开始的,所以当数据返回后,更新流程是从根组件从头开始的。

改用async await的方式势必对当前React底层架构带来挑战。最起码,会对性能优化产生不小的影响。

另一方面,async await这种方式接下来会在Server Component中实现,也就是异步的服务端组件。

服务端组件与客户端组件都是React组件,但前者在服务端渲染(SSR),后者在客户端渲染(CSR),如果都用async await,不太容易从代码层面区分两者。

总结

use​是一个「读取异步数据的原语」,他的出现是为了规范React在客户端处理异步数据的方式。

既然是原语,那么他的功能就很底层,比如不包括请求的缓存功能(由cache处理)。

之所以这么设计,是因为React​团队并不希望开发者直接使用他们。这些原语的受众是React生态中的其他库。

比如,类似SWR​、React-Query​这样的请求库,就可以结合use​,再结合自己实现的请求缓存策略(而不是使用React​提供的cache方法)

各种状态管理库,也可以将use作为其底层状态单元的容器。

值得吐槽的是,Hooks​文档中hook的限制那一节恐怕得重写了。

参考资料

[1]first-class-support-for-promises RFC:https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md。

来源:魔术师卡颂内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯