Recoil 默認值及數據級聯的使用

Recoil 中默認值及數據間的依賴

經過 Atom 可方便地設置數據的默認值,css

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

而 Selector 可方便地設置數據的級聯依賴關係,即,另外一個數據可從現有數據進行派生。html

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

    return `${fontSize}${unit}`;
  },
});

結合這兩個特色,在實現數據間存在聯動的表單時,很是方便。react

一個實際的例子

考察這樣的場景,購買雲資源時,會先選擇地域,根據所選地域再選擇該地域下的可用區。git

這裏就存在設置默認值的問題,未選擇時自動選中默認地域及對應地域下的默承認用區,也涉及數據間的級聯依賴,可選的可用區要根據地域而變化。github

呈現的效果以下:typescript


imageshell

地域及可用區的選擇api

實現地域及可用區的選擇

下面就經過 Recoil 來實現上述地域及可用區的選擇邏輯。markdown

建立示例項目

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

添加並使用 Recoil

安裝依賴:app

$ 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:

相關文章
相關標籤/搜索