Posted in

如何让 localStorage 存储变为响应式 – 掘金_AI阅读总结 — 包阅AI

包阅导读总结

1. `localStorage`、`响应式`、`React 项目`、`时区`、`数据更新`

2. 项目中有更改时区的全局和局部组件,时区数据存于 localStorage 中,当前页面不刷新时时间组件无法获取最新数据。尝试多种方法使 localStorage 数据变为响应式,最终给出成功案例,并提及用全局 store 状态管理也可实现。

3.

– 项目背景

– 有更改时区的全局和局部组件,想实现联动和实时响应。

– 时区数据存于 localStorage ,当前页不刷新组件无法更新数据。

– 失败尝试

– 使用 `useEffect` 并以 `localStorage.getItem(‘timezone’)` 作为依赖项,因每次渲染重新计算依赖项而失败。

– 用 `window` 的 `storage` 事件监听,因无法监听同页面变更而失败。

– 成功案例

– 自定义 `useRefreshLocalStorage` 函数,重写 `localStorage.setItem` 方法并监听自定义事件实现响应式。

– 小结

– 指出用全局 store 状态管理也能实现类似效果。

思维导图:

文章地址:https://juejin.cn/post/7399461786348044325

文章来源:juejin.cn

作者:Bigger

发布时间:2024/8/5 3:38

语言:中文

总字数:1649字

预计阅读时间:7分钟

评分:82分

标签:React,localStorage,响应式编程,Hooks,前端开发


以下为原文内容

本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com

背景

项目上有个更改时区的全局组件,同时还有一个可以更改时区的局部组件,想让更改时区的时候能联动起来,实时响应起来

image.png

其实每次设置完时区的数据之后是存在了前端的 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();     const connection = createConnection();    connection.connect();    return () => connection.disconnect();  }, [createOptions]);   }

失败的案例 2

一开始能想到的是监听,那就用 window 上监听事件。

在 React 应用中监听 localStorage 的变化,可以使用 window 对象的 storage 事件。这个事件在同一域名的不同文档之间共享,当某个文档修改 localStorage 时,其他文档会收到通知。

写代码…

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;

使用方式:

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";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) => {      const customEvent = event;            if (event.detail.key === localStorage_key) {                const updatedValue = customEvent.detail.newValue;        setStorageValue(updatedValue);      }    };        window.addEventListener("setItemEvent", handleSetItemEvent);        return () => {            window.removeEventListener("setItemEvent", handleSetItemEvent);            localStorage.setItem = originalSetItem;    };      }, [localStorage_key]);        return [storageValue];}export default useRefreshLocalStorage;

具体的实现步骤如上,每一步也加上了注释。

接下来就是测试了,

useTimezone 针对 timezone 数据统一封装,

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 存储变为响应式 ?

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