【问题标题】:React: ES2020 dynamic import in useEffect blocks renderingReact:ES2020 动态导入 useEffect 块渲染
【发布时间】:2021-12-10 04:16:00
【问题描述】:

据我了解,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 渲染之前导入大文件时挂起。

【问题讨论】:

  • 如果操作非常繁重(例如加载数据),任何在 UI 线程中运行的东西都会阻塞渲染。您应该将负载卸载到 Web Worker,这样它就不会阻塞 UI 线程。
  • @Terry 但浏览器不应该先完成加载,然后在导入数据时 UI 冻结吗?
  • 窗口load 事件在页面生命周期的后期触发,因为它等待所有资源/资产被加载。在您的情况下,在触发窗口加载事件之前,fetch() 操作已经返回了响应(但您的 UI 线程在接收和处理响应时锁定)。
  • @Terry 哦,好的,感谢您提供的信息。理想情况下,出于 SEO 原因,我想在加载窗口后导入数据。我不介意页面冻结一点。
  • 那你不应该在load事件监听回调中这样做吗?

标签: javascript reactjs use-effect code-splitting dynamic-import


【解决方案1】:

所有必要和有用的信息都在 Terry 的 cmets 中。这是根据 cmets 你想要的实现:

第一个目标:

出于 SEO 原因,我想在窗口加载后导入数据。

第二个目标:

在我的情况下,我尝试动态导入的文件实际上是一个需要大型数据集的函数。我想在某些状态发生变化时运行这个函数,所以我把它放在一个 useEffect 钩子中。

让我们开始吧

您可以创建一个函数来获取数据,因为您将其创建为 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;

解释:

组件确实挂载后,事件侦听器将加载 data.json on 'load' 事件。至此,第一个目标已经实现。

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

注意:第二个useEffect 中的if 块可防止在组件挂载和首次调用useEffect 后调用getData

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

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

【讨论】:

    猜你喜欢
    • 2022-10-23
    • 1970-01-01
    • 2021-04-24
    • 2020-08-03
    • 2019-12-21
    • 2018-03-25
    • 2022-01-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多