初识Recoil

Recoil作为在《React Europe 2020 Conference》上,Facebook内部释出的状态管理库,我想很多人对此都是充满兴趣的,所以花了几天时间整理了一些资料,做个入门分享。下面主要以Why Recoil -> When Recoil -> How Recoil 的顺序去介绍Recoil。

1.Why Recoil

它为什么出现呢? 问题的源头可以追溯到React框架本身,因为React规定了数据的流向是由外层组件向内层组件进行传递和更新,组件状态只能与其祖先组件进行共享,这可能会带来组件树中大量的重绘开销,组件间通信也会很不方便。

虽然Context可以解决这种问题,但是Context的问题在于:

Context can only store a single value, not an indefinite set of values each with its own consumers.(Context 只能保存一个特定值而不是与其 Consumer 共享一组不确定的值)

这会导致组件树顶部组件(状态生产者)与组件树底部组件(状态消费者)之间的代码拆分变得非常困难。

以上是官方文档给出的解释,第一次看到时候我是这样的😵。细品之后,感觉它想表达的意思其实是: 1.首先明白Context工作机制:因为每当Provider的值发生改变时, 作为Provider后代的所有Consumers都会重新渲染。 2.当存在多个Consumer时,为了避免不必要的更新,不会把所有Consumers用到的状态都一股脑塞进同一个Provider。 3.所以就出现了多个Provider-Consumer,为的就是各自管理各自的状态,互不打扰。 4.但是这就出现了另一个问题:代码耦合,拆分困难。(完结撒花🌸)

言归正传,以上这些问题促进了Recoil的诞生,同时为了达到高性能的目的,需要更加精细地操作数据流,最后Recoil计划通过不同于redux、mobx的方式解决这些问题:

  • Flexible shared state: 在 react tree 任意的地方都能灵活共享 state,并保持高性能
  • Derived data and queries: 高效可靠地根据变化的 state 进行计算
  • App-wide state observation: time travel debugging, 支持 undo, 日志持久化

Recoil就这样诞生了🐣🐣🐣🐣🐣🐣🐣

2.When Recoil

我们什么时候使用它呢?

比较现有的状态库reduxmobx,它们的区别主要在于:

状态管理库 Redux Mobx Recoil
流程 规范/复杂 自由/简单 规范/简单
依赖 redux/react-redux/redux-saga mobx/mobx-react recoil
支持 类/函数组件 类/函数组件 函数组件
学习成本
思想 函数式 响应式 hook
版本 正式 正式 测试

所以基于以上分析,项目在满足 React项目 && 使用hook && 小型项目的时候,我们可以考虑使用Recoil。

3.How Recoil

#####Recoil是如何实现状态管理的呢?

让我们看看,Recoil为了达到 Flexible shared stateDerived data and queries 这两个目的具体是怎么做的😊。

######Shared state 有一个应用基于这样一个场景,将 List 中更新一个节点,然后对应 Canvas 中的节点也更新

Recoil采用的方式是在 React Tree 上创建另一个正交的 Tree,把每个节点的 state 抽出来。每个 component 都有对应单独的一片 state,当数据更新的时候对应的组件也会更新。Recoil 把 这每一片的数据称为 Atom,Atom 是可订阅可变的 state 单元。如图所示:

######Derived Data 有这么一个场景需要根据List 计算 totalNum、totalCompletedNum之类基于List的派生状态

这是不是有点 Mobx中的@computed那味儿了🤨,Derived Data的具体思路是选取多个 Atom 进行计算,然后返回一个新的 state。因此在 Recoil 中设计了 selector 这样的 API 来选取多个 Atom 进行计算。selector 的设计和 Proxy 挺像的,属性上有 get 进行读取,有 set 进行设置,函数内部又有 get, set 操作 state。

#####那我们如何使用它呢?

直接实践一个demo吧,但是写demo之前最好先了解几点重要的概念:

  • 类似于Provider 使用Recoil需要在根节点外面包裹一层<RecoilRoot/>
  • Atom 代表状态、Selector 代表 派生状态
  • 常用的hook函数有 useRecoilValueuseSetRecoilStateuseRecoilState

这里粘几行useRecoilState的源码,相信大家就知道这三个hook大概的用途了:

1
2
3
4
5
6
7
8
9
function useRecoilState<T>(
  recoilState: RecoilState<T>,
): [T, SetterOrUpdater<T>] {
  if (__DEV__) {
    // $FlowFixMe[escaped-generic]
    validateRecoilValue(recoilState, 'useRecoilState');
  }
  return [useRecoilValue(recoilState), useSetRecoilState(recoilState)];
}

下面实现一个官方的简易demo: 查看demo

1
2
3
4
5
6
7
8
9
10
11
12
13
//App.js
import React from 'react';
import { RecoilRoot } from 'recoil';
import CharacterCounter from './pages/charCounter'

export default function App() {
  return (
    <RecoilRoot>
      <CharacterCounter />
    </RecoilRoot>
  );
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//charCounter.js
import { useRecoilState, useRecoilValue } from 'recoil'
import { textState, charCountState } from '../store/charCounterStore'

export default function CharacterCounter() {
    return (
        <div>
            <TextInput />
            <CharacterCount />
        </div>
    );
}

function TextInput() {
    const [text, setText] = useRecoilState(textState);
    const onChange = (e) => setText(e.target.value);
    return (
        <div>
            <input type="text" value={text} onChange={onChange} />
            输入文本: {text}
        </div>
    );
}

function CharacterCount() {
    const count = useRecoilValue(charCountState);
    return <>输入长度: {count}</>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//charCounterStore.js
import { atom, selector } from 'recoil';

export const textState = atom({
    key: 'textState',
    default: ''
})
export const charCountState = selector({
    key: 'charCountState',
    get: ({get})=>{
        const text = get(textState)
        return text.length;
    }
})

写完这个demo给我的感觉就是,只要熟悉hook语法,上手Recoil简直too EZ🐶,useRecoilState 很像 useState,只不过 useRecoilState 接收的初始化参数是 atom or selector 。当组件需要用到共享状态时(atom),只需要通过Recoil hook引入这个共享状态即可使用,当atom变化后,所有订阅这个atom的其他组件都会同步这个数据👍。


最后,提几点文中没提到的:

  • Recoil可以将导航视为一等公民,甚至可以对链接中的状态进行编码。
  • Recoil有与并发模式及其他 React 新特性兼容的可能性。
  • Recoil可以使用React内部的调度机制,这是Redux和Mobx不支持的。