📣 极限科技诚招搜索运维工程师(Elasticsearch/Easysearch)- 全职/北京 👉 : 立即申请加入
如何让 localStorage 数据实现实时响应

重大事项 #

📣 :重大事项提前通知!快来围观,不容错过!

极限科技一直致力于为开发者和企业提供优质的开源工具,提升整个技术生态的活力。除了维护国内最流行的分词器 analysis-ikanalysis-pinyin,也在不断推动更多高质量开源产品的诞生。

在极限科技成立三周年之际,公司宣布以下产品和工具已全面开源:

以上开源软件都可以在 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 存储变为响应式 ?

不知道大家还有什么更好的方法吗?

标签
2026 x
开源 x
赞助 x
开源生态 x
社区 x
低空经济 x
商业化 x
Easysearch x
数据分析 x
金猿奖 x
国产化 x
搜索引擎 x
Coco AI x
技术卓越奖 x
创新产品奖 x
IT168 x
APM x
Skywalking x
产品更新 x
Easy-Es x
Coco x
AI x
GitLab x
代码审核 x
人工智能 x
石油石化 x
performance x
Gitee x
投票 x
Meilisearch x
Rust x
轻量级 x
搜索百科 x
Docker x
Docker Compose x
Easyserach x
Console x
DevOps x
Elasticsearch x
国产替代 x
backup x
snapshot x
CCR x
Gateway x
esdump x
source_reuse x
ignore_above x
OpenSearch x
AWS x
Lucene x
Solr x
Easyearch x
发明专利 x
数据分区 x
国际专利 x
一等奖 x
人工智能应用创新大赛 x
bulk x
embedding x
OpenAI x
IK x
TDBC x
2025 x
信通院 x
可信数据库大会 x
搜索型数据库 x
中国数据库产业图谱 x
上海开源创新菁英荟 x
开源创新新星企业 x
Workshop x
AI 搜索 x
智能助手 x
Automation x
Logstash x
MongoDB x
开源中国 x
直播 x
merge x
Elasticsearch 9 x
GitCode x
AI搜索 x
Cloud x
rollup x
Kubernetes x
Operator x
Arm64 x
Snapshot x
S3 x
Grafana x
Opensearch x
Nginx x
直播活动 x
搜索客社区 x
Meetup x
ES x
企业搜索 x
DeepSeek x
RAG x
certificate x
windows x
Rollup x
TopN x
Filebeat x
Ubuntu x
请求限速 x
INFINI Console x
指标 x
Kibana x
多集群 x
client x
Spring Boot x
ECE x
ES Bulk x
vector database x
Postgres x
可搜索快照 x
SDK x
官网 x
Web 开发 x
Next.js x
React x
Three.js x
Metrics x
Helm x
filter x
querycache x
practice x
Agent x
localStorage x
响应式 x
时间组件 x
时区组件 x
极限科技 x
三周年 x
周年庆 x
国家高新技术企业 x
校园招聘 x
湖北工业大学 x
Tauri x
Web 开发人员 x
桌面应用开发 x
桌面端 x
Electron x
Pizza x
认证培训 x
报名 x
Scrapy x
爬虫 x
Rust开发者大会 x
docsearch x
文档搜索 x
Easyseach x
有奖征文 x
黑神话悟空 x
EKS x
征文系列 x
跨集群搜索 x
科技中小企业 x
白皮书 x
Python SDK x
数据库产业图谱 x
超大规模 x
分布式集群 x
写入限流 x
2024可信数据库发展大会 x
创新型中小企业 x
搜索数据库 x
正排索引 x
免费许可证 x
K8S x
DTC2024 x
实时搜索 x
ES国产化 x
Redis x
OOM x
测试 x
内存 x
趋势 x
AI绘画 x
Stable Diffusion x
Diffusion x
Model x
GAN x
语义搜索 x
知识图 x
向量数据库 x
中国信通院 x
星河(Galaxy) x
标杆案例 x
鲲鹏 x
鲲鹏技术认证 x
客户端 x
日志平台 x
LDAP x
Loadgen x
中国一汽 x
国内数据库 x
墨天轮 x
监控系统 x
集成测试 x
ZSTD x
Helm Charts x
国产适配 x
兆芯 x
Linux x
LoongArch x
信创适配 x
二维拆分算法 x
中国移动云 x
Vault x
加密 x
安全工具 x
kNN x
向量检索 x
图片搜索 x
Alerting x
SQL x
搜索 x
Embedding x
可信数据库 x
统信 x
海光 x
龙芯 x
restore x
Arm x
大数据企业证书 x
移动云大会 x
信通院产品评测 x
国内首家 x
数据可视化 x
北京软协 x
第十届理事会会员单位 x
Apache Arrow x
宣传片 x
大会分享 x
多集群管理 x
无缝数据迁移 x
Loadrun x
INFINI Gateway x
log4j x