大家好,我卡颂。
在 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
执行异步申请时,会由包裹 Note
的Suspense
组件渲染 加载中状态。
当申请胜利时,会从新渲染,此时 note
数据会失常返回。
当申请失败时,会由包裹 Note
的ErrorBoundary
组件解决失败逻辑。
function Note({id}) {const note = use(fetchNote(id));
return (
<div>
<h1>{note.title}</h1>
<section>{note.body}</section>
</div>
);
}
其背地的实现原理并不简单:
- 当
Note
组件首次render
,fetchNote
发动申请,会throw promise
,打断render
流程 - 以
Suspense fallback
作为渲染后果 - 当
promise
状态变动后从新触发渲染 - 依据
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
会作为客户端中解决异步数据的次要伎俩,比方:
- 解决
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
语句继续执行的(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 的限度
那一节恐怕得重写了。