Recoil 源码探索

Recoil 源码探索

作者:伍然

今天,由伍然带来《Recoil 源码探索》的分享,让我们一起学起来,造起来!

1. 简介

Recoil 是 facebook 推出的一个状态解决方案,主要的特点有:1. 高性能的渲染,2. 通过状态生成派生值。

2. 特点

高性能渲染

Recoil 源码探索_第1张图片

在 List 和 Canvas 中的第二个子组件中,如果有状态需要进行共享,常见的方法是,将状态提升到最近的公共祖先节点上,但是这样带来的问题是,如果不进行额外的处理,会造成全局的重新渲染。当然,可以使用 useMemo + immutable 进行优化。另外一种解决方法是使用 Context,使用 Context 如果想解决全局渲染的问题,需要将需要共享的值放在一个独立的 Context 中,这样带来的问题是,我们的组件上,如果有更多的状态需要共享,就需要包裹很多的 Provider,维护起来也比较困难。

Recoil 带来了一个全新的概念 Atom,将状态原子化并分散管理,Atom 是可以进行更新并且被组件所订阅的,只要 Atom 更新,可以精确地对订阅这个 Atom 的组件进行重新渲染。

Recoil 源码探索_第2张图片

Recoil 源码探索_第3张图片

派生值

可以根据 Atom,生成新的可被组件订阅的数据,类似 Vue 的计算属性。

Recoil 源码探索_第4张图片

在这里,更加强大的是,这里的 get 方法里还可以处理异步请求。

3. 核心过程源码

初始化

与 Redux 类似,在使用全局数据时,需要在根组件上包一个组件,在 Recoil 中,需要包裹上组件 RecoilRoot。

Recoil 源码探索_第5张图片

对于 RecoilRoot,其实是在我们的业务代码上,包了一层 Context,其中提供了获取 store 中的数据,更新 store 中的数据等方法。

Recoil 源码探索_第6张图片

原子状态定义

Recoil中,将每一个最小的状态单位(即无法从其他状态计算得出)定义为一个 Atom。对于每一个 Atom ,其实返回的是一个对象,包括了 key(这个 key 是从传入的 option 中解构出来的,并且需要全局唯一), 以及提供了 get 和 set 等方法。

Recoil 源码探索_第7张图片

对于 Atom 而言,必须有一个 set 方法,因为我们定义一个原子数据,肯定需要对其进行修改,否则是没有意义的。而对于 Atom 而言,其 get 方法相对简单,在 key 存在的情况下,直接从一个状态的 Map 中获取即可。

Recoil 源码探索_第8张图片

派生值定义

对于 selector,也与 Atom 一样,也提供了 get 及其他方法,然而,对于 selector 而言,是可以通过 Atom 得到的,所以 set 方法则不是必须的。

Recoil 源码探索_第9张图片

对于 get 方法,也与 Atom 有所不同,selector 的 get 多了一个缓存的处理,在没有缓存的情况下,会根据依赖的 Atom,计算得到对应的值,并将这个值进行缓存,在下次获取的时候,如果依赖的值没有变化,则可以从缓存中取值,尽可能地提高性能。

Recoil 源码探索_第10张图片

在组件中 订阅/更新 共享的值

在 Recoil 中,常见 订阅/更新 的 hooks 有 useRecoilState, useRecoilValue, useSetRecoilState。其中 useRecoilState 包含了其余两个。

Recoil 源码探索_第11张图片

订阅值

Recoil 源码探索_第12张图片

useRecoilValue 中主要的调用链如下:

useRecoilValue -> useRecoilValueLoadable -> useRecoilValueLoadable_LEGACY -> subscribeToRecoilValue

Recoil 源码探索_第13张图片

Recoil 源码探索_第14张图片

在这里,组件中通过 useRecoilValue(useRecoilState) 使用 Atom 或是 selector,会在组件上增加一个订阅,这个订阅通过 useState 来实现,当对应的 RecoilValue 更新时,对组件进行重新渲染,从而做到最小粒度更新组件。这里十分重要的是,storeState.nodeToComponentSubscriptions.set 的方法,这个方法在全局的 store 上更新了全局唯一的 key 所维护的 subscription map,通过这个 map ,在进行更新值的时候,可以准确找到对应关系,从而触发 forceUpdate 对组件进行重新渲染。

更新值

Recoil 源码探索_第15张图片

useSetRecoilState 中主要的调用链如下:

useSetRecoilState -> setRecoilValue -> queueOrPerformStateUpdate -> applyActionsToStore -> store.replaceState -> Batcher Effect -> sendEndOfBatchNotifications

这个 store.replaceState 则是在 RecoilRoot 中对 store 进行初始化时设置的方法。

Recoil 源码探索_第16张图片

其中的 notifyBatcherOfChange.current 则是在 组件中通过 useState 的形式进行 state 的更新,从而触发 useEffect,执行 sendEndOfBatchNotifications

Recoil 源码探索_第17张图片

Recoil 源码探索_第18张图片

而最终执行的这个 callback,就是上面订阅值的时候,所注册的 subscription,从而达到精确更新对应的组件。

这里需要注意的是,如果组件只需要对共享的状态进行更新,不能使用 useRecoilState,因为使用 useRecoilState 的同时会进行共享状态的订阅,造成不必要的渲染。

4. 总结

从 Recoil 的源码中,可以了解到其通过订阅和更新的分离,确保最小粒度的渲染,以及最小化了数据的单位,利用 selector 产生的派生值进行缓存,提高了计算的效率。

The End

你可能感兴趣的:(前端react.js状态管理)