当前位置: 首页 > 科技观察

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

时间:2023-03-20 14:01:02 科技观察

大家好,我是Kason。在10月13日的first-class-support-for-promisesRFC[1]中,引入了一个新的钩子——use。用什么?就是use,这个hook就叫use。这也是第一个:可以写在条件语句中的Hooks可以写在其他hook回调中的Hooks本文将讨论这个特定的hook。我们知道有什么用,async函数会和await关键字一起使用,例如:asyncfunctionload(){const{name}=awaitfetchName();returnname;}同样,在React组件中,可以使用与use类似的效果,如:functionCpn(){const{name}=use(fetchName());return

{name}

;}可以认为use的作用类似于:asyncawait中的await。生成器中的产量。使用作为“读取异步数据的原语”,可以配合Suspense实现“数据请求、加载、返回”的逻辑。比如下面的例子,当fetchNote执行异步请求时,“加载状态”会被Suspense组件包裹Note渲染出来。请求成功后会重新渲染,笔记数据会正常返回。当请求失败时,错误逻辑将由封装在Note中的ErrorBoundary组件处理。functionNote({id}){constnote=use(fetchNote(id));return(

{note.title}

{note.body}
);}背后的实现原理并不复杂:Note组件渲染时fetchNote第一次发起请求并抛出一个promise,中断渲染过程。使用Suspensefallback作为渲染结果。当promise状态改变时重新触发渲染。根据note的返回值进行渲染。事实上,这套“基于promise的中断和重新渲染流程”已经存在。use的存在是为了代替上面的过程。与上面提到的当前React中已经存在的“promise过程”不同,use只是一个“原语”(primitives),并不是一个完整的处理过程。例如,use没有“缓存承诺”的能力。例如,在下面的代码中,fetchTodo会在执行后返回一个promise,use会消费这个promise。异步函数fetchTodo(id){constdata=awaitfetchDataFromCache(`/api/todos/${id}`);返回{contents:data.contents};}functionTodo({id,isSelected}){consttodo=use(fetchTodo(id));return({todo.contents}
);}当Todo组件的idprop发生变化时,逻辑触发fetchTodo重新请求。但是当isSelected属性改变时,Todo组件也会重新渲染,fetchTodo执行后会返回一个新的promise。返回一个新的promise不一定会产生一个新的request(取决于fetchTodo的实现),但是肯定会影响React接下来的运行过程(比如性能优化打不中)。这时候就需要配合React(也在RFC中)提供的缓存API。在下面的代码中,如果id属性保持不变,fetchTodo总是返回相同的承诺:constfetchTodo=cache(async(id)=>{constdata=awaitfetchDataFromCache(`/api/todos/${id}`);返回{内容:data.contents};});使用的潜在作用目前,使用的应用场景仅限于“wrappromise”。但以后use将是客户端处理异步数据的主要手段,例如:处理contextuse(Context)可以达到和useContext(Context)一样的效果,不同的是前者可以执行在条件语句和其他钩子回调。要处理状态,您可以使用use来实现新的原生状态管理解决方案:constcurrentState=use(store);constlatestValue=使用(可观察);whynotuseasyncawait正如本文开头提到的,useprimitive类似于asyncawait中的await,那么Whynotjustuseasyncawait?像这样://Note是一个React组件asyncfunctionNote({id,isEditing}){constnote=awaitdb.posts.get(id);return(

{note.title}

{note.body}
{isEditing?:null}
);}有两个原因。一方面,异步等待的工作方式与React客户端处理异步时的逻辑不同。当await请求解析时,调用堆栈继续从await语句开始执行(生成器中的yield也是如此)。在React中,更新过程是从根组件开始的,所以当返回数据时,更新过程是从根组件开始的。改用asyncawait势必会给当前的React底层架构带来挑战。最起码会对性能优化产生相当大的影响。另一方面,asyncawait接下来会在ServerComponent中实现,它是一个异步服务端组件。服务端组件和客户端组件都是React组件,只是前者渲染在服务端(SSR),后者渲染在客户端(CSR)。如果两者都使用asyncawait,那么从代码层面就不容易区分两者。Summaryuse是一个“读取异步数据的原语”,它的出现是为了规范React在客户端处理异步数据的方式。由于是原语,所以它的功能很底层,比如不包括请求的缓存功能(由缓存处理)。这样做的原因是React团队不希望开发人员直接使用它们。这些原语的受众是React生态系统中的其他库。比如像SWR和React-Query这样的请求库可以结合使用,结合自己实现的请求缓存策略(而不是使用React提供的缓存方法),也可以使用各种状态管理库充当其基础状态单元的容器。值得抱怨的是,Hooks文档中关于hook限制的部分可能不得不重写。参考[1]first-class-support-for-promisesRFC:https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises。医学博士。