不能将 React.lazy 与 Material UI 图标一起使用

Posted

技术标签:

【中文标题】不能将 React.lazy 与 Material UI 图标一起使用【英文标题】:Cannot use React.lazy with Material UI icons 【发布时间】:2019-07-17 05:43:36 【问题描述】:

由于我的应用程序的很多地方都有很多图标,因此我想对它们使用代码拆分。

我已经创建了帮助组件来做到这一点:

import React,  FunctionComponent, Suspense  from 'react';

interface LazyMuiIconProps 
  name: string;


export const LazyMuiIcon: FunctionComponent<LazyMuiIconProps> = ( name ) => 
  console.log(name);
  const IconElement = React.lazy(() => import(`@material-ui/icons/$name`));
  // const IconElement = React.lazy(() => import(`@material-ui/icons/Home`));
  return (
    <Suspense fallback=null>
      <IconElement />
    </Suspense>
  );
;

当我使用带有固定名称的注释行 Home 它可以工作并且它会延迟加载主页图标,但是一旦我将其更改为上面的行,webpack 在编译期间崩溃并出现低级错误:

69% 建造 15623/15657 模块 34 个活动 ...terial-ui/icons/AccessibleOutlined.d.ts

[30:0x33f4320] 77272 毫秒:清除 1142.0 (1422.8) -> 1141.5 (1423.3) MB,1.4 / 0.0 ms(平均 mu = 0.300,当前 mu = 0.342) 分配失败 [30:0x33f4320] 77275 毫秒:清除 1142.3 (1423.3) -> 1141.7 (1423.8) MB,1.5 / 0.0 ms(平均 mu = 0.300, 当前 mu = 0.342) 分配失败 [30:0x33f4320] 77278 ms: 清除 1142.4 (1423.8) -> 1141.9 (1424.3) MB,1.5 / 0.0 ms(平均 mu = 0.300,当前 mu = 0.342) 分配失败

==== JS 堆栈跟踪 ========================================== =

0: ExitFrame [pc: 0x16ac4804fb5d] Security context: 0x02fea7a9d921 <JSObject>
1: add [0x2fea7a906c9](this=0x10fbf120c2e1 <Set map = 0x3f914303d81>,0x1a97709947e1 <DependenciesBlock map =

0x1c000787d3b1>) 2:新设置(又名设置)[0x2fea7a90391](this=0x056d59402691 ,0x33036eb7fbe1) 3:ConstructFrame [pc:0x16ac48009e66] 4: StubFrame [pc: 0x16ac480f932c] 5:processDependenciesBlocksForC... 70% 块图致命错误:在堆限制附近标记压缩无效 分配失败 - javascript 堆内存不足 1: 0x948d20 节点::Abort() [/usr/local/bin/node] 2: 0x9499bc 节点::OnFatalError(char const*, char const*) [/usr/local/bin/node] 3: 0xb1160e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/usr/local/bin/node] 4: 0xb11844 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/usr/local/bin/node] 5: 0xf0def2 [/usr/local/bin/node] 6: 0xf0dff8 v8::internal::Heap::CheckIneffectiveMarkCompact(unsigned long, double) [/usr/local/bin/node] 7: 0xf1a718 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/usr/local/bin/node] 8: 0xf1b22b v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCCallbackFlags) [/usr/local/bin/node] 9: 0xf1df61 v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [/usr/local/bin/node] 10: 0xee7​​e96 v8::internal::Factory::AllocateRawArray(int, v8::internal::PretenureFlag) [/usr/local/bin/node] 11: 0xee885a v8::internal::Factory::NewFixedArrayWithFiller(v8::internal::Heap::RootListIndex, int, v8::internal::Object*, v8::internal::PretenureFlag) [/usr/local/bin/node] 12: 0xee8900 v8::内部::句柄 v8::internal::Factory::NewFixedArrayWithMap(v8::internal::Heap::RootListIndex, int,v8::internal::PretenureFlag) [/usr/local/bin/node] 13: 0x108e547 v8::internal::OrderedHashTable::Allocate(v8::internal::Isolate*, int, v8::internal::PretenureFlag) [/usr/local/bin/node] 14: 0x1091ab1 v8::internal::OrderedHashTable::Rehash(v8::internal::Isolate*, v8::internal::Handle, int) [/usr/local/bin/node] 15: 0x109202b v8::internal::OrderedHashTable::EnsureGrowable(v8::internal::Isolate*, v8::内部::句柄) [/usr/local/bin/node] 16: 0x117ee2c v8::internal::Runtime_SetGrow(int, v8::internal::Object**, v8::internal::Isolate*) [/usr/local/bin/node] 17: 0x16ac4804fb5d 中止(核心转储)

我的 Babel 插件:

plugins: [
                // plugin-proposal-decorators is only needed if you're using experimental decorators in TypeScript
                "@babel/plugin-syntax-dynamic-import",
                ['@babel/plugin-proposal-decorators',  legacy: true ],
                ['@babel/plugin-proposal-class-properties',  loose: true ],
                'react-hot-loader/babel',

据我所见,它正在尝试加载所有图标,而不仅仅是一个(我将使用的实例减少到一个以更容易找到解决方案)然后它崩溃了。

更新

如果我尝试使用 react-loadable...


return Loadable(
    loader: () => import(`@material-ui/icons/$props.name`),
    loading: () => <span>icon</span>
  );

【问题讨论】:

【参考方案1】:

如果你使用 Webpack,问题是如果你想动态加载一个文件,在这种情况下是图标,webpack 默认为那个模块生成一个块。如果你使用的是 Webpack 4 或更高版本,你可以使用webpackMode: eager 来解决这个问题。因此,在这种情况下,导入将如下所示:

const IconElement = React.lazy(() => import(/* webpackMode: "eager" */`@material-ui/icons/$name`));

这基本上会强制 Webpack 将块包含到您的包中(减少 http 请求的数量) - 我认为这会使使用 Lazy / Suspense 的全部意义变得无用,但这个 Github issue 与您的问题类似,并表明不值得单独加载每个图标,即,您应该导入@material-ui/icons 包并使用您需要的图标。

更新(因为更新有问题参考react-loader):

在使用react-loader 时,您还需要指定文件的扩展名。我曾经遇到过这个问题,并通过导入指定文件的扩展名来解决它。在这种情况下:

return Loadable(
    loader: () => import(`@material-ui/icons/$props.name.ts`),
    loading: () => <span>icon</span>
  );

但我不确定它是否仍然有效,正如我在关于 Webpack 和延迟加载的回答中所建议的那样。

【讨论】:

谢谢!我使用静态导入实现了它,因为它对许多请求都是有意义的,但我会知道它以供将来重用。

以上是关于不能将 React.lazy 与 Material UI 图标一起使用的主要内容,如果未能解决你的问题,请参考以下文章

使用 React.lazy 与 webpack 动态导入?

React.lazy与Suspence实现延迟加载

react s-s-r 与代码拆分而不是 React.Lazy 的优缺点

React.lazy 会提高 React Native 的性能吗?

用React.lazy和Suspense优化React代码打包

如何使用React.lazy和Suspense进行组件延迟加载