recoil入门

1,407 阅读4分钟

Recoil是什么

中文网:www.recoiljs.cn/ 英文网:recoiljs.org/

本文的recoil版本是0.4.1

是一个 React 状态管理库

特性

  1. 极简的 React 风格设计

Recoil拥有与React一样的工作方式与原理。将其添加到您的应用中可获得快速、灵活的状态共享。目前仅支持函数组件,采用hooks编写\color{red}{目前仅支持函数组件,采用hooks编写}

  1. 数据流图

针对派生数据(Derived data)和异步查询采用纯函数以及高效订阅的方式进行处理。书写形式全是纯函数,看不见了类似actionreducerconnect等模板代码\color{red}{书写形式全是纯函数,看不见了类似action、reducer、connect等模板代码}

  1. 应用程序全局监听

通过监听应用程序中所有状态的变化来实现持久化存储,路由,时间旅行调试或撤消,并且不会影响代码分割。待补充\color{red}{待补充}

设计动机

出于兼容性和简便性的考虑,相比使用外部的全局状态,使用 React 内置的状态管理能力是个最佳的选择。但是 React 有这样一些局限性:

  • 组件间的状态共享只能通过将 state 提升至它们的公共祖先来实现,但这样做可能导致重新渲染一颗巨大的组件树。
  • Context 只能存储单一值,无法存储多个各自拥有消费者的值的集合。
  • 以上两种方式都很难将组件树的顶层(state 必须存在的地方)与叶子组件 (使用 state 的地方) 进行代码分割。

我们希望改善上述的问题的同时,不仅能保留 API 以及语义,还能使其的表现尽可能保持 React 的样子。

Recoil 定义了一个有向图 (directed graph),正交同时又天然连结于你的 React 树上。状态的变化从该图的顶点(我们称之为 atom)开始,流经纯函数 (我们称之为 selector) 再传入组件。基于这样的实现:

  • 我们可以定义无需模板代码的 API,共享的状态拥有与 React 本地 state 一样简单的 get/set 接口 (当然如果需要,也可以使用 reducer 等进行封装)。
  • 我们有了与 Concurrent 模式及其他 React 新特性兼容的可能性。
  • 状态的定义是渐进式和分布式的,这使代码分割成为可能。
  • 无需修改对应的组件,就能将它们本地的 state 用派生数据替换。
  • 无需修改对应的组件,就能将派生数据在同步与异步间切换。
  • 我们能将导航视为头等概念,甚至可以将状态的转变编码进链接中。
  • 可以很轻松地以可回溯的方式持久化整个应用的状态,持久化的状态不会因为应用的改变而丢失。

如何使用(安装略过)

  1. 引用RecoilRoot,并包裹于根组件,类似React.Context
import React from 'react';
import { RecoilRoot } from 'recoil';

function App() {
    return (
        <RecoilRoot>
            <Root />
        </RecoilRoot>
    );
}
  1. 常见概念及API
  • Atom

一个 atom 代表一个状态。Atom 可在任意组件中进行读写。读取 atom 值的组件隐式订阅了该 atom,因此任何 atom 的更新都将致使使用对应 atom 的组件重新渲染

const textState = atom({
  key: 'textState', // unique ID (with respect to other atoms/selectors)
  default: '', // default value (aka initial value)
});
  • 获取Atom->useRecoilState、useRecoilValue、useSetRecoilState

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

function TextInput() {
  const [text, setText] = useRecoilState(textState);
  // const text = useRecoilValue(textState);
  // const setText = useSetRecoilState(textState);
  // useRecoilState可拆分成useRecoilValue和useSetRecoilState
  const onChange = (event) => {
    setText(event.target.value);
  };

  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo: {text}
    </div>
  );
}
  • Selector

selector 代表一个派生状态,派生状态是状态的转换。你可以将派生状态视为将状态传递给以某种方式修改给定状态的纯函数的输出

const charCountState = selector({
  key: 'charCountState', // unique ID (with respect to other atoms/selectors)
  get: ({get}) => {
    const text = get(textState);

    return text.length;
  },
});
  • 获取Selector-> useRecoilValue

function CharacterCount() {
  const count = useRecoilValue(charCountState);

  return <>Character Count: {count}</>;
}
  • atomFamily (todo)

  • selectorFamily (todo)

  1. 异步组件 selector 的接口总是相同的,所以使用这个 selector 的组件不需要关心它是用同步 atom 状态、派生 selector 状态或者异步查询来实现的!

但是,由于 React 的渲染函数是同步的,在 Promise 解决之前,它将渲染什么?Recoil 的设计配合 React Suspense 处理待定 (pending) 数据。如果用 Suspense 边界包裹你的组件,会捕捉到任何仍在 pending 中的后代,并渲染一个回调 UI。

import { memo, Suspense } from 'react';
import { selector, useRecoilValue } from 'recoil';

function CurrentTime() {
  const currentTime = selector({
    key: 'currentTime',
    get: async ({get}) => {
      return await new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(new Date().valueOf())
        }, 2500)
      })
    },
  });
  const value = useRecoilValue(currentTime);
  return <div>{value}</div>;
}

function App() {

  return (
    <div className="App">
      <Suspense fallback={<div>加载中。。。</div>}>
        <CurrentTime />
      </Suspense>
    </div>
  );
}

export default memo(App);

其他

  1. 关于key是否由用户管理,社区存在讨论
  2. key重复的话,会有警告
recoil.js:565 Duplicate atom key "textState". This is a FATAL ERROR in production. But it is safe to ignore this warning if it occurred because of hot module replacement.
  1. 浏览器的开发者工具 Recoilize,目前存在版本不兼容问题,可及时关注,也可根据官方示例,做个简单log插件

chrome.google.com/webstore/de…