乐趣区

关于前端:第一个可以在条件语句中使用的原生hook诞生了

大家好,我卡颂。

在 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 的限度 那一节恐怕得重写了。

退出移动版