动态

详情 返回 返回

如何讓 localStorage 數據實現實時響應 - 动态 详情

重大事項

📣 :重大事項提前通知!快來圍觀,不容錯過!

極限科技 一直致力於為開發者和企業提供優質的開源工具,提升整個技術生態的活力。除了維護國內最流行的分詞器 analysis-ikanalysis-pinyin,也在不斷推動更多高質量開源產品的誕生。

在極限科技成立三週年之際,公司宣佈以下產品和工具已全面開源:

  • INFINI Framework
  • INFINI Gateway
  • INFINI Console
  • INFINI Agent
  • INFINI Loadgen
  • INFINI Coco AI

以上開源軟件都可以在 Github 上面找到: https://github.com/infinilabs

希望大家都能給個免費的 Star🌟 支持一下!!!

背景

在開發公司項目 INFINI Cloud(暫未開源,敬請期待。 不過此次開源的同類項目有 INFINI Console)的時候,該項目上有個更改時區的全局組件,同時還有一個可以更改時區的局部組件,想讓更改時區的時候能聯動起來,實時響應起來

image.png

Tip:如果有人對該時間組件感興趣,可以移步 https://github.com/infinilabs/ui-common,同時也希望收到您 Star🌟 支持,也希望和大家一起共建。

其實每次設置完時區的數據之後是存在了前端的 localStorage 裏邊,時間組件裏邊也是從 localStorage 拿去默認值來回顯。如果當前頁面不刷新,那麼時間組件就不能更新到最新的 localStorage 數據。

怎麼才能讓 localStorage 存儲的數也變成響應式呢?

實現

  1. 應該寫個公共的方法,不僅僅時區數據能用,萬一後邊其他數據也能用。
  2. 項目是 React 項目,那就寫個 hook
  3. 怎麼才能讓 localStorage 數據變成響應式呢?監聽?

失敗的案例 1

首先想到的是按照下邊這種方式做,

useEffect(() => {
  console.log(11111, localStorage.getItem("timezone"));
}, [localStorage.getItem("timezone")]);

得到的測試結果肯定是失敗的,但是為啥失敗?我們也應該知道一下。查了資料説,使用 localStorage.getItem('timezone') 作為依賴項會導致每次渲染都重新計算依賴項,這不是正確的做法。

具體看一下官方文檔:useEffect(setup, dependencies?)

在此説一下第二個參數 dependencies

可選 dependenciessetup 代碼中引用的所有響應式值的列表。響應式值包括 props、state 以及所有直接在組件內部聲明的變量和函數。如果你的代碼檢查工具 配置了 React,那麼它將驗證是否每個響應式值都被正確地指定為一個依賴項。依賴項列表的元素數量必須是固定的,並且必須像 [dep1, dep2, dep3] 這樣內聯編寫。React 將使用 Object.is 來比較每個依賴項和它先前的值。如果省略此參數,則在每次重新渲染組件之後,將重新運行 Effect 函數。

  • 如果你的一些依賴項是組件內部定義的對象或函數,則存在這樣的風險,即它們將 導致 Effect 過多地重新運行。要解決這個問題,請刪除不必要的 對象 和 函數 依賴項。你還可以 抽離狀態更新 和 非響應式的邏輯 到 Effect 之外。

如果你的 Effect 依賴於在渲染期間創建的對象或函數,則它可能會頻繁運行。例如,此 Effect 在每次渲染後重新連接,因為 createOptions 函數 在每次渲染時都不同:

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState("");

  function createOptions() {
    // 🚩 此函數在每次重新渲染都從頭開始創建
    return {
      serverUrl: serverUrl,
      roomId: roomId,
    };
  }

  useEffect(() => {
    const options = createOptions(); // 它在 Effect 中被使用
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]); // 🚩 因此,此依賴項在每次重新渲染都是不同的
  // ...
}

失敗的案例 2

一開始能想到的是監聽,那就用 window 上監聽事件。

在 React 應用中監聽 localStorage 的變化,可以使用 window 對象的 storage 事件。這個事件在同一域名的不同文檔之間共享,當某個文檔修改 localStorage 時,其他文檔會收到通知。

寫代碼...

// useRefreshLocalStorage.js
import { useState, useEffect } from "react";

const useRefreshLocalStorage = (key) => {
  const [storageValue, setStorageValue] = useState(localStorage.getItem(key));

  useEffect(() => {
    const handleStorageChange = (event) => {
      if (event.key === key) {
        setStorageValue(event.newValue);
      }
    };

    window.addEventListener("storage", handleStorageChange);

    return () => {
      window.removeEventListener("storage", handleStorageChange);
    };
  }, [key]);

  return [storageValue];
};

export default useRefreshLocalStorage;

使用方式:

// useTimezone.js
import { useState, useEffect } from "react";

import { getTimezone, timezoneKey } from "@/utils/utils";
import useRefreshLocalStorage from "./useRefreshLocalStorage";

function useTimezone() {
  const [TimeZone, setTimeZone] = useState(() => getTimezone());
  const [storageValue] = useRefreshLocalStorage(timezoneKey);

  useEffect(() => {
    setTimeZone(() => getTimezone());
  }, [storageValue]);

  return [TimeZone];
}

export default useTimezone;

經過測試,失敗了,沒有效果!!!那到底怎麼回事呢?哪裏出現問題了?查閲資料經過思考,可能出現的問題的原因有:只能監聽同源的兩個頁面之間的 storage 變更,沒法監聽同一個頁面的變更。

成功的案例

import { useState, useEffect } from "react";

// 自定義 Hook,用於監聽 localStorage 中指定鍵的變化
function useRefreshLocalStorage(localStorage_key) {
  // 檢查 localStorage_key 是否有效
  if (!localStorage_key || typeof localStorage_key !== "string") {
    return [null];
  }

  // 創建一個狀態變量來保存 localStorage 中的值
  const [storageValue, setStorageValue] = useState(
    localStorage.getItem(localStorage_key)
  );

  useEffect(() => {
    // 保存原始的 localStorage.setItem 方法
    const originalSetItem = localStorage.setItem;
    // 重寫 localStorage.setItem 方法,添加事件觸發邏輯
    localStorage.setItem = function (key, newValue) {
      // 創建一個自定義事件,用於通知 localStorage 的變化
      const setItemEvent = new CustomEvent("setItemEvent", {
        detail: { key, newValue },
      });
      // 觸發自定義事件
      window.dispatchEvent(setItemEvent);
      // 調用原始的 localStorage.setItem 方法
      originalSetItem.apply(this, [key, newValue]);
    };

    // 事件處理函數,用於處理自定義事件
    const handleSetItemEvent = (event) => {
      const customEvent = event;
      // 檢查事件的鍵是否是我們關心的 localStorage_key
      if (event.detail.key === localStorage_key) {
        // 更新狀態變量 storageValue
        const updatedValue = customEvent.detail.newValue;
        setStorageValue(updatedValue);
      }
    };

    // 添加自定義事件的監聽器
    window.addEventListener("setItemEvent", handleSetItemEvent);

    // 清除事件監聽器和還原原始方法
    return () => {
      // 移除自定義事件監聽器
      window.removeEventListener("setItemEvent", handleSetItemEvent);
      // 還原原始的 localStorage.setItem 方法
      localStorage.setItem = originalSetItem;
    };
    // 依賴數組,只在 localStorage_key 變化時重新運行 useEffect
  }, [localStorage_key]);

  // 返回當前的 storageValue
  // 為啥沒有返回 setStorageValue ?
  // 因為想讓用户直接操作自己真實的 “setValue” 方法,這裏只做一個只讀。
  return [storageValue];
}

export default useRefreshLocalStorage;

具體的實現步驟如上,每一步也加上了註釋。

接下來就是測試了,

useTimezone 針對 timezone 數據統一封裝,

// useTimezone.js
import { useState, useEffect } from "react";

import { getTimezone, timezoneKey } from "@/utils/utils";
import useRefreshLocalStorage from "./useRefreshLocalStorage";

function useTimezone() {
  const [TimeZone, setTimeZone] = useState(() => getTimezone());
  const [storageValue] = useRefreshLocalStorage(timezoneKey);

  useEffect(() => {
    setTimeZone(() => getTimezone());
  }, [storageValue]);

  return [TimeZone];
}

export default useTimezone;

具體的業務頁面組件中使用,

// 頁面中
// ...
import useTimezone from "@/hooks/useTimezone";

export default (props) => {
  // ...
  const [TimeZone] = useTimezone();

  useEffect(()=>{
      console.log(11111, TimeZone)
  },[TimeZone)
}

測試結果必須是成功的啊!!!

小結

其實想要做到該效果,用全局 store 狀態管理也能做到,條條大路通羅馬嘛!不過本次需求由於歷史原因一直使用的是 localStorage ,索性就想着 如何讓 localStorage 存儲變為響應式 ?

不知道大家還有什麼更好的方法嗎?

作者:Rain9,極限科技(INFINI Labs) 高級前端開發工程師。
user avatar Dream-new 头像 aser1989 头像 huaweichenai 头像 talkcss 头像 sysin 头像 shenyongweiwudemaozi 头像 xiangjiaochihuanggua 头像
点赞 7 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.