zoukankan      html  css  js  c++  java
  • Recoil 默认值及数据级联的使用

    Recoil 中默认值及数据间的依赖

    通过 Atom 可方便地设置数据的默认值,

    const fontSizeState = atom({
      key: 'fontSizeState',
      default: 14,
    });

    而 Selector 可方便地设置数据的级联依赖关系,即,另一个数据可从现有数据进行派生。

    const fontSizeLabelState = selector({
      key: 'fontSizeLabelState',
      get: ({get}) => {
        const fontSize = get(fontSizeState);
        const unit = 'px';
    
        return `${fontSize}${unit}`;
      },
    });

    结合这两个特点,在实现数据间存在联动的表单时,非常方便。

    一个实际的例子

    考察这样的场景,购买云资源时,会先选择地域,根据所选地域再选择该地域下的可用区。

    这里就存在设置默认值的问题,未选择时自动选中默认地域及对应地域下的默认可用区,也涉及数据间的级联依赖,可选的可用区要根据地域而变化。

    呈现的效果如下:


    image

    地域及可用区的选择

    实现地域及可用区的选择

    下面就通过 Recoil 来实现上述地域及可用区的选择逻辑。

    创建示例项目

    $  yarn create react-app recoil-nest-select --template typescript

    添加并使用 Recoil

    安装依赖:

    $ yarn add recoil

    使用 Recoil, 首先将应用包裹在 RecoilRoot 中:

    index.tsx

    import { RecoilRoot } from "recoil";
    
    ReactDOM.render(
      <React.StrictMode>
        <RecoilRoot>
          <Suspense fallback="loading...">
            <App />
          </Suspense>
        </RecoilRoot>
      </React.StrictMode>,
      document.getElementById("root")
    );

    添加 appState.ts 文件存放 Recoil 状态数据,目前先定义好地域和可用区的类型,

    appState.ts

    interface IZone {
      id: string;
      name: string;
    }
    
    interface IRegion {
      id: string;
      name: string;
      zones: IZone[];
    }

    添加假数据

    根据上面定义的类型,添加假数据:

    mock.ts

    export const mockRegionData = [
      {
        id: "beijing",
        name: "北京",
        zones: [
          {
            id: "beijing-zone-1",
            name: "北京一区",
          },
          {
            id: "beijing-zone-2",
            name: "北京二区",
          },
          {
            id: "beijing-zone-3",
            name: "北京三区",
          },
        ],
      },
      {
        id: "shanghai",
        name: "上海",
        zones: [
          {
            id: "shanghai-zone-1",
            name: "上海一区",
          },
          {
            id: "shanghai-zone-2",
            name: "上海二区",
          },
          {
            id: "shanghai-zone-3",
            name: "上海三区",
          },
        ],
      },
      {
        id: "guangzhou",
        name: "广州",
        zones: [
          {
            id: "guangzhou-zone-1",
            name: "广州一区",
          },
          {
            id: "guangzhou-zone-2",
            name: "广州二区",
          },
        ],
      },
    ];

    添加状态数据

    添加地域及可用区状态数据,先看地域数据,该数据用来生成地域的下拉框。真实情况下,该数据来自异步请求,这里通过 Promise 模拟异步数据。

    appState.ts

    import { atom, selector } from "recoil";
    import { mockRegionData } from "./mock";
    
    export const regionsState = selector({
      key: "regionsState",
      get: ({ get }) => {
        return Promise.resolve<IRegion[]>(mockRegionData);
      },
    });

    添加一个状态用于保存当前选中的地域:

    appState.ts

    export const regionState = atom({
      key: "regionState",
      default: selector({
        key: "regionState/Default",
        get: ({ get }) => {
          const regions = get(regionsState);
          return regions[0];
        },
      }),
    });

    这里通过使用 atom 并指定默认值为地域第一个数据,达到下拉框默认选中第一个的目的。

    添加地域选择组件

    添加地域选择组件,使用上面创建的地域数据。

    RegionSelect.tsx

    import React from "react";
    import { useRecoilState, useRecoilValue } from "recoil";
    import { regionsState, regionState } from "./appState";
    
    export function RegionSelect() {
      const regions = useRecoilValue(regionsState);
      const [region, setRegion] = useRecoilState(regionState);
      return (
        <label htmlFor="regionId">
          地域:
          <select
            name="regionId"
            id="regionId"
            value={region.id}
            onChange={(event) => {
              const regionId = event.target.value;
              const region = regions.find((region) => region.id === regionId);
              setRegion(region!);
            }}
          >
            {regions.map((region) => (
              <option key={region.id} value={region.id}>
                {region.name}
              </option>
            ))}
          </select>
        </label>
      );
    }

    至此地域部分完成,可用区同理,只不过可用区的拉下数据依赖于当前选中的地域。

    添加可用区状态数据及下拉组件

    appState.tsx

    export const zonesState = selector({
      key: "zonesState",
      get: ({ get }) => {
        const region = get(regionState);
        return region.zones;
      },
    });
    
    export const zoneState = atom({
      key: "zoneState",
      default: selector({
        key: "zoneState/default",
        get: ({ get }) => {
          return get(zonesState)[0];
        },
      }),
    });

    可选择的可用区依赖于当前选中的地域,通过 const region = get(regionState); 实现获取到当前选中地域的目的。

    可用区的默认值也是拿到当前可选的所有地域,然后取第一个,return get(zonesState)[0];

    ZoneSelect.tsx

    import React from "react";
    import { useRecoilState, useRecoilValue } from "recoil";
    import { zonesState, zoneState } from "./appState";
    
    export function ZoneSelect() {
      const zones = useRecoilValue(zonesState);
      const [zone, setZone] = useRecoilState(zoneState);
      return (
        <label htmlFor="zoneId">
          可用区:
          <select
            name="zoneId"
            id="zoneId"
            value={zone.id}
            onChange={(event) => {
              const zoneId = event.target.value;
              const zone = zones.find((zone) => zone.id === zoneId);
              setZone(zone!);
            }}
          >
            {zones.map((zone) => (
              <option key={zone.id} value={zone.id}>
                {zone.name}
              </option>
            ))}
          </select>
        </label>
      );
    }

    展示当前地域及可用区

    将前面两个下拉框展示出来,同时展示当前地域及可用区。

    App.tsx

    import React from "react";
    import { useRecoilValue } from "recoil";
    import "./App.css";
    import { regionState, zoneState } from "./appState";
    import { RegionSelect } from "./RegionSelect";
    import { ZoneSelect } from "./ZoneSelect";
    
    function App() {
      const region = useRecoilValue(regionState);
      const zone = useRecoilValue(zoneState);
      return (
        <div className="App">
          <p>region:{region.name}</p>
          <p>zone:{zone.name}</p>
          <RegionSelect />
          <ZoneSelect />
        </div>
      );
    }
    
    export default App;

    至此完成了整个程序的实现。

    最终效果

    来看看效果:

    Screen Recording 2021-02-19 at 9 17 55 PM mov

    地域及可用区联动效果

    带默认值的状态未自动更新的问题

    上面的实现乍一看实现了功能,但进行可用区的选择之后问题便会暴露。

    Screen Recording 2021-02-19 at 9 25 19 PM mov

    可用区未联动的问题

    可以看到可用区更新后,再切换地域,虽然下拉框中可选的可用区更新了,但实际上当前可用区的值停留在了上一次选中的值,并没有与地域联动。如果不是把可用区展示出来,不容易发现这里的问题,具有一定迷惑性。

    看看可用区下拉值 zones 的来源不难发现,

    export const zonesState = selector({
      key: "zonesState",
      get: ({ get }) => {
        const region = get(regionState);
        return region.zones;
      },
    });

    因为可用区是从当前选中的地域数据 regionState 中获取的,当变更地域后,regionState 更新,导致 zonesState 更新,所以下拉框能正确同步,没问题。

    再看看当前选中的可用区 zoneState

    export const zoneState = atom({
      key: "zoneState",
      default: selector({
        key: "zoneState/default",
        get: ({ get }) => {
          return get(zonesState)[0];
        },
      }),
    });

    它通过 atom 承载,同时指定了默认值,为 zonesState 中第一个数据。

    当切换地域时,zonesState 确实更新了,进而 zoneState 的默认值也会重新获取,所以始终会默认选中第一个可用区。

    当我们手动进行了可用区选择时,在可用区下拉组件中,

          <select
            name="zoneId"
            id="zoneId"
            value={zone.id}
    +       onChange={(event) => {
    +        const zoneId = event.target.value;
    +       const zone = zones.find((zone) => zone.id === zoneId);
    +         setZone(zone!);
            }}
          >
            {zones.map((zone) => (
              <option key={zone.id} value={zone.id}>
                {zone.name}
              </option>
            ))}
          </select>

    onChange 事件的回调中通过 setZone 更新了 zoneState,此时可用区 zoneState 已经有一个人为设置的值,默认值就不起作用了,因此在切换地域后,zoneState 仍为这里 onChange 设置的值。

    手动添加依赖

    直接的修复方式可以在可用区组件中监听地域的变化,当地域变化后,设置一次可用区。

    export function ZoneSelect() {
    + const region = useRecoilValue(regionState);
      const zones = useRecoilValue(zonesState);
      const [zone, setZone] = useRecoilState(zoneState);
    
    + console.log("zone:", zone.id);
    
    + useEffect(() => {
    +   setZone(zones[0]);
    + }, [region]);
    
      return (
        <label htmlFor="zoneId">
         …
        </label>
      );
    }

    能达到目的,但通过打印出来的可用区值来看,当地域切换后,可用区的值更新并不及时,首先会打印出一个错误的值,待 useEffect 执行完毕后,才打印出正确的值,即,这种方式的修复,有滞后性。

    Screen Recording 2021-02-20 at 10 55 28 AM mov

    通过 `useEffect` 方式来修正,可用区更新会滞后

    useResetRecoilState

    查阅 Recoil 文档,发现 useResetRecoilState 可用于重置状态到默认值。

    这里的思路可以是,在地域变化后,重置一下可用区,这样之前手动选择的值便失效,可用区恢复到默认状态。

    export function RegionSelect() {
      const regions = useRecoilValue(regionsState);
      const [region, setRegion] = useRecoilState(regionState);
    + const resetZone = useResetRecoilState(zoneState);
      return (
        <label htmlFor="regionId">
          地域:
          <select
            name="regionId"
            id="regionId"
            value={region.id}
            onChange={(event) => {
              const regionId = event.target.value;
              const region = regions.find((region) => region.id === regionId);
    +         resetZone();
              setRegion(region!);
            }}
          >
            {regions.map((region) => (
              <option key={region.id} value={region.id}>
                {region.name}
              </option>
            ))}
          </select>
        </label>
      );
    }

    这里 resetZonesetRegion 的顺序不影响,都能达到目的。

    Screen Recording 2021-02-20 at 11 15 56 AM mov

    通过 `useResetRecoilState` 重置状态到默认值

    通过打印的值来看,一切正常,问题得以修正。

    相关资源

    The text was updated successfully, but these errors were encountered:

    CC BY-NC-SA 署名-非商业性使用-相同方式共享
  • 相关阅读:
    Ceph14.2.5 RBD块存储的实战配置和详细介绍,不看后悔! -- <3>
    常见SQL命令总结学习 -- <1>
    全网最详细的新手入门Mysql命令和基础,小白必看!
    全网最详细的Linux命令系列-nl命令
    全网最详细的Linux命令系列-cat命令
    全网最详细的Linux命令系列-touch命令
    全网最详细的Ceph14.2.5集群部署及配置文件详解,快来看看吧! -- <2>
    什么是Ceph存储?什么是分布式存储?简单明了带你学Ceph -- <1>
    一款专注于阅读的博客园主题-(cnblogs-theme-silence)
    Prometheus 配置文件中 metric_relabel_configs 配置--转载
  • 原文地址:https://www.cnblogs.com/Wayou/p/14638785.html
Copyright © 2011-2022 走看看