大家好,我卡颂。
在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的限度
那一节恐怕得重写了。