编程 如何通过封装自定义Hook来实现对localStorage的响应式监听,解决了在更改时区时相关组件无法实时更新的问题

2024-11-19 03:08:13 +0800 CST views 943

响应式监听 localStorage 存储?封装个自定义 Hook 不就好了!

背景

项目中有一个全局更改时区的组件,同时还有一个局部更改时区的组件。需求是当更改时区时,所有相关组件能够实时联动并更新。

其实每次设置完时区的数据之后是存储在前端的 localStorage 中,组件也是从 localStorage 中获取默认值并显示。如果当前页面不刷新,时间组件就无法更新到最新的 localStorage 数据。那么,如何让 localStorage 存储的数据变成响应式呢?

实现

为了解决这一问题,我们可以编写一个公共的自定义 Hook,这样不仅仅是时区数据,其他需要监听 localStorage 变化的数据也可以复用这一逻辑。

失败的案例 1

一开始尝试使用 useEffect 监听 localStorage 变化:

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

测试结果失败。原因是 localStorage.getItem('timezone') 在每次渲染时都会重新计算,导致无法正确监听到变化。

失败的案例 2

接下来尝试使用 windowstorage 事件来监听 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;

然而,这种方式也失败了,因为 storage 事件只能监听同源的两个页面之间的 localStorage 变化,无法监听同一个页面的变化。

成功的案例

最终的解决方案是重写 localStorage.setItem 方法,通过触发自定义事件来实现响应式监听。

import { useState, useEffect } from "react";

function useRefreshLocalStorage(localStorage_key) {
  if (!localStorage_key || typeof localStorage_key !== "string") {
    return [null];
  }
  
  const [storageValue, setStorageValue] = useState(localStorage.getItem(localStorage_key));

  useEffect(() => {
    const originalSetItem = localStorage.setItem;

    localStorage.setItem = function(key, newValue) {
      const setItemEvent = new CustomEvent("setItemEvent", { detail: { key, newValue } });
      window.dispatchEvent(setItemEvent);
      originalSetItem.apply(this, [key, newValue]);
    };

    const handleSetItemEvent = (event) => {
      if (event.detail.key === localStorage_key) {
        setStorageValue(event.detail.newValue);
      }
    };

    window.addEventListener("setItemEvent", handleSetItemEvent);

    return () => {
      window.removeEventListener("setItemEvent", handleSetItemEvent);
      localStorage.setItem = originalSetItem;
    };
  }, [localStorage_key]);

  return [storageValue];
}

export default useRefreshLocalStorage;

使用示例

封装一个用于管理时区数据的自定义 Hook:

// 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 来存储数据,因此我们通过封装自定义 Hook 实现了 localStorage 数据的响应式更新。


复制全文 生成海报 前端开发 React 状态管理 Hooks

推荐文章

Python设计模式之工厂模式详解
2024-11-19 09:36:23 +0800 CST
Nginx 状态监控与日志分析
2024-11-19 09:36:18 +0800 CST
在Rust项目中使用SQLite数据库
2024-11-19 08:48:00 +0800 CST
聚合支付管理系统
2025-07-23 13:33:30 +0800 CST
js常用通用函数
2024-11-17 05:57:52 +0800 CST
一些好玩且实用的开源AI工具
2024-11-19 09:31:57 +0800 CST
使用xshell上传和下载文件
2024-11-18 12:55:11 +0800 CST
Vue 3 中的 Fragments 是什么?
2024-11-17 17:05:46 +0800 CST
Vue3 实现页面上下滑动方案
2025-06-28 17:07:57 +0800 CST
120个实用CSS技巧汇总合集
2025-06-23 13:19:55 +0800 CST
Dropzone.js实现文件拖放上传功能
2024-11-18 18:28:02 +0800 CST
MySQL死锁 - 更新插入导致死锁
2024-11-19 05:53:50 +0800 CST
如何在 Vue 3 中使用 Vuex 4?
2024-11-17 04:57:52 +0800 CST
程序员茄子在线接单