zoukankan      html  css  js  c++  java
  • 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] = useRecoilState(fontSizeState);
      return (
        <button onClick={() => setFontSize((size) => 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 = get(fontSizeState); // 可以访问其他 atom 或 selector, 同时会建立依赖关系。
        const unit = 'px';
        return `${fontSize}${unit}`; // 简单的静态依赖,也可以动态依赖
      },
      // 如果也提供了 set,则会返回一个可写的 RecoilState 对象。
    });
    // 只读 selector
    function FontButton() {
      const [fontSize, setFontSize] = useRecoilState(fontSizeState);
      const fontSizeLabel = useRecoilValue(fontSizeLabelState); // 只读的 selector 使用 useRecoilState 方法,入参可以是 atom 或 selector
      return (
        <>
          <div>Current font size: ${fontSizeLabel}</div>
    
          <button onClick={() => setFontSize(fontSize + 1)} style={{fontSize}}>
            Click to Enlarge
          </button>
        </>
      );
    }
    // 可读写的 selector
    // 这个简单的选择器实质上包装了一个原子以添加一个附加字段。
    const proxySelector = selector({
      key: 'ProxySelector',
      get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
      set: ({set}, newValue) => set(myAtom, newValue), // 更改会沿数据流图传播回上游。
    });
    // 更改数据用法,需判断是否是初始值(这个用法有点麻烦呀。。。
    import {selector, DefaultValue} from 'recoil';
    const transformSelector = selector({
      key: 'TransformSelector',
      get: ({get}) => get(myAtom) * 100,
      set: ({set}, newValue) =>
        set(myAtom, newValue instanceof DefaultValue ? newValue : newValue / 100),
    });
    // 和 DefaultValue 对应的还有一个重置方法:
    resetTemp = useResetRecoilState(tempCelcius);
    resetTemp();
    

    动态依赖

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

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

    异步 Selector

    import {selector, useRecoilValue} from 'recoil';
    
    const myQuery = selector({
      key: 'MyDBQuery',
      get: async () => {
        const response = await fetch(getMyRequestUrl());
        if (response.error) {
          throw response.error;
        }
        return response.json(); // 返回一个 Promise
      },
    });
    
    function QueryResults() {
    	// 配合上 Suspense 使用后,调用上和同步没有区别。
      // 不配合 Suspense 使用,需要自行处理它的三种状态。
      const queryResults = useRecoilValue(myQuery);
      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 = useRecoilValueLoadable(userNameQuery(userID));
      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} = get(currentUserInfoQuery);
        const friends = get(waitForAll(
          friendList.map(friendID => userInfoQuery(friendID))
        ));
        return friends;
      },
    });
    // 处理部分数据的UI增量更新
    const friendsInfoQuery = selector({
      key: 'FriendsInfoQuery',
      get: ({get}) => {
        const {friendList} = get(currentUserInfoQuery);
        const friendLoadables = get(waitForNone(
          friendList.map(friendID => userInfoQuery(friendID))
        ));
        return friendLoadables
          .filter(({state}) => state === 'hasValue')
          .map(({contents}) => contents);
      },
    });
    
  • 相关阅读:
    c#中使用log4net工具记录日志
    由命名空间函数而引发思考--js中的对象赋值问题
    浅析C#中的Attribute[转]
    剑指offer_输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果
    剑指offer_输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径
    剑指offer_输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字
    HashSet TreeSet 中元素顺序问题(未解决)
    找出某个String中出现次数最多的字符,并输出次数(字符较长)
    MyBatis、JDBC、Hibernate区别
    String.equals用法注意
  • 原文地址:https://www.cnblogs.com/bldxh/p/6358793.html
Copyright © 2011-2022 走看看