React的useEffect和DOM事件处理程序之间的执行顺序

我在下面编写了一个自定义上下文提供程序,该应用程序将应用程序设置保存为Context,并将其保存在localStorage中(感谢Alex Krush的post)。

我添加了initialized标志,以避免在组件安装后立即保存从localStorage获取的值(useEffect将在componentDidmount的时间运行,并尝试将获取的值写入存储。)

import React,{ useCallback,useEffect,useReducer,useRef } from 'react';

const storageKey = 'name';
const defaultvalue = 'John Doe';

const initializer = (initialValue) => localStorage.getItem(storageKey) || initialValue;
const reducer = (value,newValue) => newValue;

const CachedContext = React.createContext();

const CachedContextProvider = (props) => {
  const [value,setvalue] = useReducer(reducer,defaultvalue,initializer);
  const initialized = useRef(false);

  // save the updated value as a side-effect
  useEffect(() => {
    if (initialized.current) {
      localStorage.setItem(storageKey,value);
    } else {
      initialized.current = true; // skip saving for the first time
    }
  },[value]);

  return (
    <CachedContext.Provider value={[value,setvalue]}>
      {props.children}
    </CachedContext.Provider>
  );
};

用法:

const App = (props) => {
  return <CachedContextProvider><Name name='Jane Doe' /></CachedContextProvider>;
}

const Name = (props) => {
  const [name,setName] = useContext(CachedContext);

  useEffect(() => {
    setName(props.name);
  },[props.name]);
}

然后,我想让我的自定义上下文检测到另一个窗口对目标存储所做的更改。我将handleStorageEvent添加到CachedContextProvider来监听存储事件:

  // re-initialize when the storage has been modified by another window
  const handleStorageEvent = useCallback((e) => {
    if (e.key === storageKey) {
      initialized.current = false; // <-- is it safe???
      setvalue(initializer(defaultvalue));
    }
  },[]);

  useEffect(() => {
    if (typeof window !== 'undefined') {
      window.addEventListener('storage',handleStorageEvent);
      return () => {
        window.removeEventListener('storage',handleStorageEvent);
      };    
    }
  },[]);

我担心的是是否可以安全地将initialized重置为false 以避免写回获取的值。我担心多进程设置中的以下情况:

  1. 窗口1运行setvalue('Harry Potter')
  2. 窗口2运行setvalue('Harry Potter')
  3. 窗口2运行localStorage.setItem以响应value上的更新
  4. 窗口1中的
  5. handleStorageEvent检测到存储更改,并将其initializedvalue初始化为false'Harry Potter'
  6. 窗口1尝试运行localStorage.setItem,但是它没有任何作用,因为窗口2已经将value设置为'Harry Potter',并且React可能会判断没有变化。结果,initialized将保留为false
  7. 窗口1运行setvalue('Ron Weasley')。它会更新value,但不会保存,因为initialized === false。有机会失去应用程序设置的值

我认为这与ReactuseEffect和DOM事件处理程序之间的执行顺序有关。有人知道怎么做对吗?

iCMS 回答:React的useEffect和DOM事件处理程序之间的执行顺序

我可能会添加某种测试,以查看在每种可能的情况下会发生什么。

但是,这是我的理论:在第5步中,窗口1将不会尝试运行localStorage.setItem(如您所说),因为初始化只是设置为false。而是将其初始化为true。因此,第6步应能按预期工作,这应该不是问题。

,

我与同事讨论了这个问题,最后找到了解决方案。他指出,新的React Fiber引擎不应该确保副作用的执行顺序,并建议在状态中添加一个修订号

这里是一个例子。即使集revision不变,递增的useEffect仍将始终调用value。订阅者从state.value获得Provider,而不必担心基础revision

import React,{ useCallback,useEffect,useReducer,useRef } from 'react';

const storageKey = 'name';
const defaultValue = 'John Doe';

const orDefault(value) = (value) =>
  (typeof value !== 'undefined' && value !== null) ? value : defaultValue;

const initializer = (arg) => ({
  value: orDefault(localStorage.getItem(storageKey)),revision: 0,});

const reducer = (state,newValue) => ({
  value: newValue,revision: state.revision + 1,});

const useCachedValue = () => {
  const [state,setState] = useReducer(reducer,null,initializer);
  const initialized = useRef(false);

  // save the updated value as a side-effect
  useEffect(() => {
    if (initialized.current) {
      localStorage.setItem(storageKey,state.value);
    } else {
      // skip saving just after the (re-)initialization
      initialized.current = true;
    }
  },[state]);

  // re-initialize when the storage has been modified by another window
  const handleStorageEvent = useCallback((e) => {
    if (e.key === null || e.key === storageKey) {
      initialized.current = false;
      setState(orDefault(e.newValue));
    }
  },[]);

  useEffect(() => {
    if (typeof window !== 'undefined') {
      window.addEventListener('storage',handleStorageEvent);
      return () => {
        window.removeEventListener('storage',handleStorageEvent);
      };
    }
  },[]);

  return [state.value,setState];
};

const Context = React.createContext();

const Provider = (props) => {
  const cachedValue = useCachedValue();
  return (
    <Context.Provider value={cachedValue}>
      {props.children}
    </Context.Provider>
  );
};
本文链接:https://www.f2er.com/2235122.html

大家都在问