在ReactEurope2020大会上,Facebook软件工程师DaveMcCabe介绍了一个新的状态管理库Recoil。Recoil仍处于实验阶段,现在已在一些Facebook内部产品的生产环境中使用。毕竟是官方的状态管理框架。之前没时间仔细研究,所以趁着国庆看了一下,分享给大家。State和Context的问题假设我们有如下场景:有两个组件List和Canvas,List中的一个节点更新后,Canvas中的节点也随之更新。最常见的做法是通过父组件给List和Canvas组件分发一个状态。显然,每次状态发生变化时,所有节点都会完全更新。当然,我们也可以使用ContextAPI。我们将节点的状态存储在上下文中。只要Provider中的props发生变化,Provider的所有后代用户都会重新渲染。为了避免全量渲染的问题,我们可以将每个子节点存储在一个单独的Context中,这样每增加一个节点就需要多一层Provider。但是如果子节点是动态添加的呢?我们还需要动态增加Provider,这会导致整棵树再次重新渲染,这显然不符合预期。Recoil的引入Recoil本身就是为了解决React全局数据流管理的问题,采用原子状态去中心化管理的设计模式。Recoil提出了一个新的状态管理单元Atom,它是可更新和可订阅的。当Atom更新时,每个订阅的组件都将使用新值重新呈现。如果您在多个组件中使用相同的Atom,所有这些组件将共享它们的状态。你可以把Atom看作是一个状态的集合,改变一个Atom只会渲染特定的子组件,不会让整个父组件重新渲染。Redux或Mobx不可能吗?因为React本身提供的状态state很难跨组件状态共享,所以我们在开发的时候一般会使用一些其他的库比如Redux和Mobx来帮助我们管理状态。这些库被广泛使用,我们没有遇到任何重大问题,那么为什么Facebook推出新的状态管理框架?当然,你可以使用Redux和Mobx,也没有问题。主要原因是它们本身不是React库。我们使用这些库的功能来实现状态管理。虽然Redux本身提供了强大的状态管理能力,但是使用成本非常高,需要编写很多冗长的代码。另外,异步处理或者缓存计算都不是这些库本身的能力,甚至需要借助其他外部库。而且他们无法访问React内部的调度器,Recoil在后台使用React本身的状态,未来可以提供并发模式的能力。基本使用初始化使用recoil状态的组件需要使用RecoilRootwrapped:importReactfrom'react';import{RecoilRoot,atom,selector,useRecoilState,useRecoilValue,useSetRecoilState}from'recoil';functionApp(){return();}定义状态上面我们已经提到了Atom的概念,Atom是一个新的状态,但是它不同于传统的状态,它可以被任何组件订阅,当一个Atom被更新时,每一个订阅的组件将使用新值重新呈现。首先,让我们定义一个Atom:exportconstnameState=atom({key:'nameState',default:'ConardLi'});这种方式意味着你不需要像Redux那样集中定义状态,你可以像Mobx那样分散数据。任何地方。要创建一个Atom,必须提供一个键,该键在RecoilRoot范围内必须是唯一的,并且必须提供一个默认值,该默认值可以是静态值、函数甚至是异步函数。订阅和更新状态Recoil使用Hooks来订阅和更新状态。下面是常用的三个API:useRecoilState:一个类似于useState的Hook,可以获取atom的值和setter函数重新渲染useRecoilValue:只获取状态import{nameState}from'./store'//useRecoilStateconstNameInput=()=>{const[name,setName]=useRecoilState(nameState);constonChange=(event)=>{setName(event.target.value);};return<
Name:{name}
>;}//useRecoilValueconstSomeOtherComponentWithName=()=>{constname=useRecoilValue(nameState);return
{name}
;}//useSetRecoilStateconstSomeOtherComponentThatSetsName=()=>{constsetName=useSetRecoilState(nameState);return
setName('JonDoe')}>SetName;}派生状态选择器代表一段派生状态,它允许我们构建依赖于其他原子的状态。它有一个强制性的get函数,就像redux的reselect或MobX的@computed一样。constlengthState=selector({key:'lengthState',get:({get})=>{consttext=get(nameState);returntext.length;},});functionNameLength(){constlength=useRecoilValue(charLengthState);返回<>NameLength:{length}>;}选择器是一个纯函数:对于一组给定的输入,它们应该始终产生相同的结果(至少在应用程序的生命周期内)。这很重要,因为选择器可能会被执行一次或多次,可能会重新启动并可能被缓存。异步状态反冲提供了通过数据流图将状态和派生状态映射到React组件的方法。真正强大的特性是图中的函数也可以是异步的。这使我们可以轻松地在异步React组件渲染函数中使用异步函数。借助Recoil,您可以在选择器的数据流图中无缝混合同步和异步功能。只需从选择器获取回调中返回Promise,而不是返回值本身。例如,在下面的示例中,如果用户名存储在我们需要查询的某个数据库中,那么我们要做的就是返回一个Promise或使用一个异步函数。如果userID发生变化,将自动重新执行新的查询。结果会被缓存,所以查询只会对每个唯一的输入执行一次(所以一定要保持选择器的纯净,否则缓存的结果会和最新的值不一致)。constuserNameQuery=selector({key:'userName',get:async({get})=>{constresponse=awaitmyDBQuery({userID:get(currentUserIDState),});returnresponse.name;},});functionCurrentUserInfo(){constuserName=useRecoilValue(userNameQuery);return{userName}
;}Recoil推荐使用Suspense,它会捕获所有的异步状态,使用ErrorBoundary捕获错误:functionMyApp(){return(Loading... );}总结Recoil提倡去中心化状态管理,这种模式和Mobx很像,用起来感觉有点像observable+computed的模式,但是它的API和核心思想设计不像Mobx那么简单易懂,而是有点复杂,新手会比较吃力开始吧。成本。在使用上,完全拥抱函数式Hooks的使用,不提供Component的使用。目前,我们也可以使用原生的HooksAPI来实现状态管理。我们也可以使用useMemo创建派生状态,Recoil的useRecoilState和selector也更像是对useContext和useMemo的封装。但毕竟是Facebook官方推出的状态管理框架。它专注于高性能,可以利用React内部的调度机制,包括它承诺即将支持的并发模式。这还是非常值得期待的。另外,其自身的原子状态去中心化管理模式、读写分离、按需渲染、派生缓存等思想还是非常值得学习的。