Recoil 中多級數據聯動及數據重置的合理作法

前情回顧

書接上回,前面引出了在數據存在級聯的狀況下,各下拉框之間的默認值及值變化的處理。簡單回顧一下:html

場景是:git

  • 地域下拉決定可選的可用區
  • 默認選中第一個地域,經過設置 atomdefault 字段
  • 默認選中該地域下第一個可用區,經過設置 atomdefault 字段

問題:github

  • 手動選擇一下可用區,此時更新了可用區的值
  • 手動選擇一下地域,此時更新了地域,可用區下拉框同步更新,此時實際可用區的值爲前面手動選擇的舊值,界面上卻展現的新可用區的第一個。

解決:markdown

  • 在地域選擇組件中,當地域發生變化時,重置一下可用區使其回到默認值。

新的問題

進一步實踐,會發現這種解決方式存在缺陷,在多級級聯的狀況下,好比三個下拉框 A->B->C,A 決定 B, B 決定 C,按照這個解決思路,優化

  • 在 A 變化時須要重置 B,C
  • B 變化時須要重置 C

這顯然不科學,很是冗餘。同時從組件解耦的角度來看,A,B 須要知道誰依賴了本身從而重置它們,這種耦合很是難以維護。atom

所以應該反過來,將解決問題的邏輯囿於組件自身才是科學的作法。url

因而 A 無論其餘,只管本身隨便隨便怎麼變化,B 中監聽 A 變化而後作出反應以重置本身,C 監聽 B 的變化以重置本身。這樣邏輯作到了內聚無耦合。spa

而以前文章中之因此沒用這種方式,是由於發現該方式具備滯後性,組件內部會停留在錯誤的值上渲染一次。code

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 執行完後纔會打印正確的值。若是在舊值的情形下依賴該狀態去作了些業務邏輯,勢必會致使錯誤,好比拿這個舊值去發起請求。htm

狀態的正確使用

細思會發現,上面之因此會有這種錯誤是由於姿式沒對,倘若咱們要使用可用區的值,應該在 useEffect 中進行,亦即:

  useEffect(() => {
    // do sth with zone
    console.log("zone", zone.id);
  }, [zone]);

此時打印就會獲得正確的結果。

按照這個邏輯修正後的組件及聯動關係就成了:

RegionSelect.tsx

export function RegionSelect() {
  const regions = useRecoilValue(regionsState);
  const [region, setRegion] = useRecoilState(regionState);

  return (
    <label htmlFor="regionId">
      Region:
      <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.id}
          </option>
        ))}
      </select>
    </label>
  );
}

ZoneSelect.tsx

export function ZoneSelect() {
  const zones = useRecoilValue(zonesState);
  const [zone, setZone] = useRecoilState(zoneState);
  const resetZone = useResetRecoilState(zoneState);
  const region = useRecoilValue(regionState);

  // region 變化後重置 zone
  useEffect(() => {
    resetZone();
  }, [region, resetZone]);

  useEffect(() => {
    // do sth with zone
    console.log("zone", zone.id);
  }, [zone]);

  return (
    <label htmlFor="zoneId">
      Zone:
      <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.id}
          </option>
        ))}
      </select>
    </label>
  );
}

優化數據的依賴關係

進一步思考,致使可用區須要重置的直接緣由其實並非地域發生了變化,而是地域發生變化後,可用區下拉框的可選項發生了變化,亦即 zonesState。既然下拉選項變化了,固然須要重置默認值爲新的下拉選項中的第一個。因此可用區組件中直接監聽下拉選項,而非地域。

export function ZoneSelect() {
  const zones = useRecoilValue(zonesState);
  const [zone, setZone] = useRecoilState(zoneState);
  const resetZone = useResetRecoilState(zoneState);

  useEffect(() => {
    resetZone();
  }, [resetZone, zones]);

  useEffect(() => {
    // do sth with zone
    console.log("zone", zone.id);
  }, [zone]);

  return (
    <label htmlFor="zoneId">
      Zone:
      <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.id}
          </option>
        ))}
      </select>
    </label>
  );
}

這樣一來,組件內部就清爽多了,只有自身相關的數據,甚至都去掉了對 regionState 的使用。

selector 派生數據的隱形橋樑功能

這裏實際上是 zonesState 做爲橋樑自動完成了對 region 的監聽,由於 zonesStateselector,它是從 regionState 派生出來的數據,在 regionState 發生變化時,會由 Recoil 負責更新。

其餘

最後,示例代碼參見 wayou/recoil-nest-select

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

相關文章
相關標籤/搜索