Recoil 了解一下

简介

主要解决状态共享的问题,推崇状态和派生数据更细粒度控制,Redux 的数据结构是树而 Recoil 是有向图。Recoil 还是一个实验性的解决方案。

状态改变的流向是从图的根 atoms 共享状态),通过纯函数 selectors 派生数据),最后流入组件中。

特性:

共享状态具有与 React 本地状态相同的简单 get / set 接口。”出于兼容性和简便性的考虑,最好使用 React 的内置状态管理功能,而不是外部全局状态: 好像暗示了什么”
天然支持 suspense,里面关键的两个点,atom 和 selector 都支持返回 loadable。支持 react 后续的 cocurrent 模式,甚至是后续的其他新特性。
定义是增量式和分布式的,通过观察应用程序中的所有状态更改来实现持久性,路由,时间旅行调试或撤消操作,而不会影响代码拆分。
可以用派生数据替换状态,而无需修改使用状态的组件,派生数据可以抹平同步异步的调用差异。
方便 state 持久化,原理是从 atom 处进行持久化操作,提供订阅 atom 变更的钩子,还正在开发直接订阅全部 atom 变更的钩子方法。

重要概念

Atoms

即状态,可更新和订阅,可以用 atom 直接替代 React 本地组件的 state 使用。

// 创建 atom
const fontSizeState = atom{
  key: 'fontSizeState',
  default: 14,
});
// 读写 atom: useRecoilState(相当于useState)
function FontButton) {
  const [fontSize, setFontSize] = useRecoilStatefontSizeState);
  return 
    <button onClick={) => setFontSizesize) => size + 1)} style={{fontSize}}>
      Click to Enlarge
    </button>
  );
}

Selectors

是个纯函数,用于处理一个功能或者派生状态。入参是 atom 或其他 selector,依赖的 atom 或 selector 变更时,该 selector 会重新计算。组件也可直接订阅 selector,其改变时也会重新渲染。

selector 的设计是为了避免冗余的状态。不再需要 reducers 去同步状态,取而代之的是根据最小状态集自动计算功能。

从组件角度看,atom 和 selector 有相同的接口,可以替换使用。

// 创建 selector
const fontSizeLabelState = selector{
  key: 'fontSizeLabelState',
  get: {get}) => { // get 属性是要计算的函数,如果只提供 get 即为只读,会返回一个只读的 RecoilValueReadOnly对象。
    const fontSize = getfontSizeState); // 可以访问其他 atom 或 selector, 同时会建立依赖关系。
    const unit = 'px';
    return `${fontSize}${unit}`; // 简单的静态依赖,也可以动态依赖
  },
  // 如果也提供了 set,则会返回一个可写的 RecoilState 对象。
});
// 只读 selector
function FontButton) {
  const [fontSize, setFontSize] = useRecoilStatefontSizeState);
  const fontSizeLabel = useRecoilValuefontSizeLabelState); // 只读的 selector 使用 useRecoilState 方法,入参可以是 atom 或 selector
  return 
    <>
      <div>Current font size: ${fontSizeLabel}</div>

      <button onClick={) => setFontSizefontSize + 1)} style={{fontSize}}>
        Click to Enlarge
      </button>
    </>
  );
}
// 可读写的 selector
// 这个简单的选择器实质上包装了一个原子以添加一个附加字段。
const proxySelector = selector{
  key: 'ProxySelector',
  get: {get}) => {...getmyAtom), extraField: 'hi'}),
  set: {set}, newValue) => setmyAtom, newValue), // 更改会沿数据流图传播回上游。
});
// 更改数据用法,需判断是否是初始值(这个用法有点麻烦呀。。。
import {selector, DefaultValue} from 'recoil';
const transformSelector = selector{
  key: 'TransformSelector',
  get: {get}) => getmyAtom) * 100,
  set: {set}, newValue) =>
    setmyAtom, newValue instanceof DefaultValue ? newValue : newValue / 100),
});
// 和 DefaultValue 对应的还有一个重置方法:
resetTemp = useResetRecoilStatetempCelcius);
resetTemp);

动态依赖

依赖是在 selector 计算的时候,根据实际的依赖动态确定的。因此也可以根据先前依赖关系的值,动态使用其他附加依赖。

const toggleState = atom{key: 'Toggle', default: false});
const mySelector = selector{
  key: 'MySelector',
  get: {get}) => {
    const toggle = gettoggleState);
    if toggle) { // 动态添加依赖
      return getselectorA); 
    } else {
      return getselectorB);
    }
  },
});

异步 Selector

import {selector, useRecoilValue} from 'recoil';

const myQuery = selector{
  key: 'MyDBQuery',
  get: async ) => {
    const response = await fetchgetMyRequestUrl));
    if response.error) {
      throw response.error;
    }
    return response.json); // 返回一个 Promise
  },
});

function QueryResults) {
	// 配合上 Suspense 使用后,调用上和同步没有区别。
  // 不配合 Suspense 使用,需要自行处理它的三种状态。
  const queryResults = useRecoilValuemyQuery);
  return 
    <div>
      {queryResults.foo}
    </div>
  );
}

function ResultsSection) {
  return 
  	<RecoilRoot> // 必须
	    <ErrorBoundary> // 按需
        <React.Suspense fallback={<div>Loading...</div>}> // 强烈推荐,不然会很麻烦
          <QueryResults />
        </React.Suspense>
      <ErrorBoundary>
    </RecoilRoot>
  );
}

KEY

Atom、 Selector 都要求保证 key 的全局唯一性,甚至着重说了不唯一是个错误的用法。key 可用于调试,持久化以及某些高级 API,这些 API 可查看所有 atom 的图。在 Recoil 中无论 atom 还是 selector,都是注册成一个 node,treestate 中会保存他们对于 key 的依赖,包括组件下游、 node 下游 和 node 上游,这也是为什么要求 key 唯一的原因。每个 node 都是单独声明的,而不是像 redux 或者 mobx 那样耦合在一个对象里面,这种松耦合的方式,也是 vue3 现在做的,比较高级的说法是 runtime tree shaking。

结语

再从以下几个维度看一下这个技术:

TypeScript 支持:最新版本0.0.10 已支持。✅
友好的异步支持:支持,且支持 Suspense 使用。✅
同时支持 Class 与 Hooks 组件:只支持 hooks。❌
使用简单:
– 和 react 本身的 state 理念一致,理解成本低。✅
– 配合 react16 新特性的强大功能,导致 API 众多,不容易记忆,不知后续会不会优化 API ❌
– redux ➕ react-redux ➕ redux-sage ➕ reselector ≈ recoil (虽然源码代码量也是相对可观的)

最后,Recoil 还是个在实验阶段,因此文中的一些用法或者说的到的问题,后面很有可能都会变更,各位看官主要感受该技术的定位即可,等正式版发布后,如果有需要会修正文章内容。

附录

不配合 React Suspense 使用异步 selector 的姿势

function UserInfo{userID}) {
  const userNameLoadable = useRecoilValueLoadableuserNameQueryuserID));
  switch userNameLoadable.state) {
    case 'hasValue':
      return <div>{userNameLoadable.contents}</div>;
    case 'loading':
      return <div>Loading...</div>;
    case 'hasError':
      throw userNameLoadable.contents;
  }
}

支持后续的cocurrent使用姿势

// 并发请求
const friendsInfoQuery = selector{
  key: 'FriendsInfoQuery',
  get: {get}) => {
    const {friendList} = getcurrentUserInfoQuery);
    const friends = getwaitForAll
      friendList.mapfriendID => userInfoQueryfriendID))
    ));
    return friends;
  },
});
// 处理部分数据的UI增量更新
const friendsInfoQuery = selector{
  key: 'FriendsInfoQuery',
  get: {get}) => {
    const {friendList} = getcurrentUserInfoQuery);
    const friendLoadables = getwaitForNone
      friendList.mapfriendID => userInfoQueryfriendID))
    ));
    return friendLoadables
      .filter{state}) => state === 'hasValue')
      .map{contents}) => contents);
  },
});

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注