大家好,我卡颂。

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

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

  • 能够在条件语句中书写的hook
  • 能够在其余hook回调中书写的hook

本文来聊聊这个非凡的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的作用相似于:

  • async await中的await
  • generator中的yield

use作为读取异步数据的原语,能够配合Suspense实现数据申请、加载、返回的逻辑。

举个例子,下述例子中,当fetchNote执行异步申请时,会由包裹NoteSuspense组件渲染加载中状态

当申请胜利时,会从新渲染,此时note数据会失常返回。

当申请失败时,会由包裹NoteErrorBoundary组件解决失败逻辑。

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

其背地的实现原理并不简单:

  1. Note组件首次renderfetchNote发动申请,会throw promise,打断render流程
  2. Suspense fallback作为渲染后果
  3. promise状态变动后从新触发渲染
  4. 依据note的返回值渲染

实际上这套基于promise的打断、从新渲染流程以后曾经存在了。use的存在就是为了替换上述流程。

与以后React中曾经存在的上述promise流程不同,use仅仅是个原语primitives),并不是残缺的解决流程。

比方,use并没有缓存promise的能力。

举个例子,在上面代码中fetchTodo执行后会返回一个promiseuse会生产这个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组件也会从新renderfetchTodo执行后会返回一个新的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会作为客户端中解决异步数据的次要伎俩,比方:

  • 解决context

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

  • 解决state

能够利用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语句继续执行的(generatoryield也是这样)。

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

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

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

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

总结

use是一个读取异步数据的原语,他的呈现是为了标准React在客户端解决异步数据的形式。

既然是原语,那么他的性能就很底层,比方不包含申请的缓存性能(由cache解决)。

之所以这么设计,是因为React团队并不心愿开发者间接应用他们。这些原语的受众是React生态中的其余库。

比方,相似SWRReact-Query这样的申请库,就能够联合use,再联合本人实现的申请缓存策略(而不是应用React提供的cache办法)

各种状态治理库,也能够将use作为其底层状态单元的容器。

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