使用跨不同组件的自定义挂钩来响应、更新和检测 localStorage 更改

React, update and detect localStorage changes with a custom hook across different components

我有一个自定义挂钩来管理 localStorage,如下所示:

import { useEffect, useState } from 'react'

export default function useLocalStorage(key, initValue) {

    const [state, setState] = useState(() => {
        const value = localStorage.getItem(key);
        if (value !== null) {
            return JSON.parse(value);
        }

        localStorage.setItem(key, JSON.stringify(initValue));
        return initValue;
    })

    useEffect(() => {
        localStorage.setItem(key, state);
    }, [key, state])
    
    return [state, setState];
}

此自定义挂钩已声明为对一个屏幕中的两个或多个组件使用相同的本地存储键。例如

const [state, setState] = useLocalStorage('key', false);

此时,由于使用useLocalStorage的组件要根据localStorage的状态变化进行工作,我们尝试使用useLocalStorage返回的状态作为第二个参数useEffect.

useEffect(() => {
   foobar()
}, [state]);

我希望 useEffect 中的 foobar() 函数在声明此 useEffect 的所有组件中的“状态”发生变化时起作用。但是,foobar() 仅适用于使用相同 useEffect 的多个组件中的一个组件。为什么会这样?我如何让它按照我想要的方式工作?

您应该为存储更改设置一个事件侦听器,以便获得更新后的值。请参阅下面的代码和有关存储事件的注释:

import { useEffect, useState } from 'react'

export default function useLocalStorage(key, initValue) {
  const [state, setState] = useState(() => {
    const value = localStorage.getItem(key);
    if (value !== null) {
      return JSON.parse(value);
    }

    localStorage.setItem(key, JSON.stringify(initValue));
    window.dispatchEvent(new Event("storage"));
    return initValue;
  });

  useEffect(() => {
    localStorage.setItem(key, state);
    window.dispatchEvent(new Event("storage"));
  }, [key, state]);

  useEffect(() => {
    const listenStorageChange = () => {
      setState(() => {
        const value = localStorage.getItem(key);
        if (value !== null) {
          return JSON.parse(value);
        }

        localStorage.setItem(key, JSON.stringify(initValue));
        window.dispatchEvent(new Event("storage"));
        return initValue;
      });
    };
    window.addEventListener("storage", listenStorageChange);
    return () => window.removeEventListener("storage", listenStorageChange);
  }, []);

  return [state, setState];
}

您可能想知道为什么使用这个 dispatchEvent。那么这里是 MDN 所说的 storage event:

The storage event of the Window interface fires when a storage area (localStorage) has been modified in the context of another document.

这个 dispatchEvent 是必需的,因为我们还需要监听同一文档中的更改。