React:在 useEffect 中 ES2020 动态导入会阻塞渲染

React: ES2020 dynamic import in useEffect blocks rendering

据我了解,useEffect 中的异步代码运行时不会阻塞渲染进程。所以如果我有这样的组件:

const App = () => {
  useEffect(() => {
    const log = () => console.log("window loaded");
    window.addEventListener("load", log);
    return () => {
      window.removeEventListener("load", log);
    };
  }, []);

  useEffect(() => {
    const getData = async () => {
      console.log("begin");
      const response = await fetch(
        "https://jsonplaceholder.typicode.com/todos/1"
      );
      const data = await response.json();
      console.log("end");
    };
    getData();
  }, []);

  return null;
};

控制台输出(按顺序):

begin
window loaded
end

但是,如果我在 useEffect 中使用 ES2020 动态导入:

const App = () => {
  useEffect(() => {
    const log = () => console.log("window loaded");
    window.addEventListener("load", log);
    return () => {
      window.removeEventListener("load", log);
    };
  }, []);

  useEffect(() => {
    const getData = async () => {
      console.log("begin");
      const data = await import("./data.json");
      console.log("end");
    };
    getData();
  }, []);

  return null;
};

输出是(按顺序):

begin
end
window loaded

这是一个问题,因为当 data.json 非常大时,浏览器会在 React 渲染任何内容之前导入大文件时挂起。

所有必要和有用的信息都在特里的评论中。根据评论,这里是你想要的实现:

第一个进球:

I would like to import the data after window has loaded for SEO reasons.

第二个进球:

In my case the file I'm trying to dynamically import is actually a function that requires a large dataset. And I want to run this function whenever some state has changed so that's why I put it in a useEffect hook.

开始吧

您可以创建一个函数来获取您创建数据时的数据,如 getDatauseCallback 挂钩,以便在任何地方使用它而不会出现问题。

import React, {useEffect, useState, useCallback} from 'react';

function App() {
  const [data, setData] = useState({});
  const [counter, setCounter] = useState(0);

  const getData = useCallback(async () => {
    try {
      const result = await import('./data.json');
      setData(result);
    } catch (error) {
      // do a proper action for failure case
      console.log(error);
    }
  }, []);

  useEffect(() => {
    window.addEventListener('load', () => {
      getData().then(() => console.log('data loaded successfully'));
    });

    return () => {
      window.removeEventListener('load', () => {
        console.log('page unmounted');
      });
    };
  }, [getData]);

  useEffect(() => {
    if (counter) {
      getData().then(() => console.log('re load data after the counter state get change'));
    }
  }, [getData, counter]);

  return (
    <div>
      <button onClick={() => setCounter((prevState) => prevState + 1)}>Increase Counter</button>
    </div>
  );
}

export default App;

解释:

组件挂载后,事件侦听器将在 'load' 事件上加载 data.json。至此,第一个目标已经达成。

我使用示例和简单的计数器来演示状态变化和重新加载 data.json 场景。现在,随着 counter 值的每次更改,getData 函数都会调用,因为第二个 useEffect 在其依赖项数组中有计数器。现在第二个目标已经达成。

注意: 第二个 useEffect 中的 if 块阻止 getData 在组件挂载并首次调用 useEffect

注意:不要忘记在失败情况下使用 catch 块。

注意:我把getData的结果设置为data状态,你可能对这个结果做不同的处理,但是其他逻辑都一样。