为什么 useEffect 不会在文档更改时触发?

时间:2021-02-22 10:19:35

标签: reactjs react-hooks use-effect

每次 HTML lang 属性更改时,我都在编写自定义钩子来设置新的语言环境,但是当使用 javascript 更改 useEffect 时,似乎 document.documentElement.lang 钩子不会触发。我知道如何解决这个问题,我的问题是为什么它会这样?

export const useLocale = (): LocaleObject => {
  const [lang, setLang] = useState<string>(document.documentElement.lang);

  useEffect(() => {
    setLang(document.documentElement.lang);
  }, [document.documentElement.lang]); // useEffect is not triggered when document.documentElement.lang changes

  return locale[lang];
};

1 个答案:

答案 0 :(得分:1)

正如@DennisVash 在他的 comment 中所写:

<块引用>

为什么?因为对 document.documentElement.lang 的更改不会触发 钩子。只有渲染会触发钩子,如果 lang 改变了 回调将被执行。

但是,由于更改属性实际上会更改 DOM 中的 lang 属性值,因此您可以使用 MutationObserver 来跟踪 lang 属性值。

我创建了一个自定义的 useMutationObserver 钩子来跟踪 DOM 中的变化,并以 useLocale 为基础。

const { useRef, useEffect, useState, useCallback } = React;

const useMutationObserver = (domNodeSelector, observerOptions, cb) => {
  useEffect(() => {
    const targetNode = document.querySelector(domNodeSelector);
    
    const observer = new MutationObserver(cb);
    
    observer.observe(targetNode, observerOptions);
    
    return () => {
      observer.disconnect();
    };
  }, [domNodeSelector, observerOptions, cb]);
}

const options = { attributes: true };

const useLocale = () => {
  const [lang, setLang] = useState(document.documentElement.lang);
  
  const handler = useCallback(mutationList => { 
    mutationList.forEach(mutation => {
      if(mutation.type !== 'attributes' || mutation.attributeName !== 'lang') return;

      setLang(document.documentElement.lang);
    });
  }, []);
  
  useMutationObserver('html', options, handler);

  return lang; // locale[lang]
};

const Demo = () => {
  const locale = useLocale();

  return <div>{locale}</div>;
};

document.documentElement.lang = 'en'; // base lang

ReactDOM.render(
  <Demo />,
  root
);

// example - changing the lang
setTimeout(() => document.documentElement.lang = 'fr', 1000);
setTimeout(() => document.documentElement.lang = 'ru', 3000);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

<div id="root"></div>