开发者问题收集

在一个 webpack 项目中使用带有 react hooks 的另一个项目会导致错误

2020-01-17
3167

有三个使用 react-app-rewired 定制的 create-react-app ,都使用以下软件包的相同版本:

"react": "^16.12.0",
"react-app-rewired": "^2.1.5",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.0",

应用程序 1,“主”应用程序是其他两个应用程序的一个非常简单的外壳, public/index.html 看起来像这样:

<body>
    <div class="main-app"></div>
    <div class="sub-app-1"></div>
    <div class="sub-app-2"></div>
    <script src="sub-app-1/static/js/bundle.js" async></script>
    <script src="sub-app-1/static/js/0.chunk.js" async></script>
    <script src="sub-app-1/static/js/main.chunk.js" async></script>
    <script src="sub-app-2/static/js/bundle.js" async></script>
    <script src="sub-app-2/static/js/0.chunk.js" async></script>
    <script src="sub-app-2/static/js/main.chunk.js" async></script>
</body>

这运行良好,所有三个应用程序都正确呈现。现在要求略有变化,其中 sub-app-2 中的唯一组件例如 <PictureFrame /> 需要包含在 sub-app-1 中,我在主项目中创建了一个存根组件,如下所示:

const NullPictureFrame = () => {
    return <div></div>;
}

export const PictureFrame = (props: Props) => {
    const forceUpdate = useForceUpdate();
    useEventListener(window, "PictureFrameComponent.Initialized" as string, () => forceUpdate());

    const PictureFrame = window.PictureFrameComponent || NullPictureFrame;
    return <PictureFrame />
}

我认为钩子的细节并不重要,但它们以独立方式运行时确实有效。 sub-app-2 执行的操作与此类似

window.PictureFrameComponent = () => <PictureFrame />

理论上似乎可行,但在实践中出现以下错误

The above error occurred in the <PictureFrame> component:
    in PictureFrame (at src/index.tsx:17)
    in Router (at src/index.tsx:16)
    in Unknown (at PictureFrames/index.tsx:21)
    in PictureFrame (at src/index.tsx:32) <--- in `main-app`

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/docs/error-boundaries.html to learn more about error boundaries. index.js:1
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and fix this problem.

main-appsub-app-2 正在加载两个不同版本的 react ,这在使用钩子时会导致问题。

我已尝试根据 此 github 线程 中的建议更新我的 webpack 配置,但看来 sub-app-2 无法使用此方法找到对 react 的引用。我的 main-app/config-overrides.js 如下所示:

config.resolve.alias["react"] = path.resolve(__dirname, "./node_modules/react");
config.resolve.alias["react-dom"] = path.resolve(__dirname, "./node_modules/react-dom");

而我的 sub-app-1/config-overrides.js 如下所示:

config.externals = config.externals || {};
config.externals.react = {
    root: "React",
    commonjs2: "react",
    commonjs: "react",
    amd: "react"
};
config.externals["react-dom"] = {
    root: "ReactDOM",
    commonjs2: "react-dom",
    commonjs: "react-dom",
    amd: "react-dom"
};

这不会产生任何错误,但 sub-app-2 中的代码未被 webpack 初始化,因为无法找到 react / react-dom


编辑 1 :澄清

  • 所讨论的 3 个应用程序是 3 个完全独立的 (git) 项目,只是都使用相同的依赖项。
  • 项目之间允许的唯一“依赖关系”是使用全局变量、事件、 Window.postMessage 或类似变量的这种非常松散的耦合。
  • 我的初始方法 const PictureFrame = window.PictureFrameComponent || NullPictureFrame; 无需使用钩子即可正常工作,团队至今仍在使用钩子,这强化了上述非常松散的依赖关系的想法
  • 有一种“显而易见”的方法,即使“react”、“react-dom”以及任何依赖于它们的东西成为 main-appexternals 的一部分,并以这种方式加载它们
3个回答

我看到您采取了将多个项目放在一个目录/存储库/代码库下的方法,我认为这不是最好的方法。您应该拆分并独立拥有所有项目,一个库子项目,其中包含所有共享代码/组件,然后让每个项目都可以访问其所需的内容。

您可以阅读我写的一篇关于 将 React Native 组件共享到多个项目 的文章,我建议了 3 种在项目之间共享代码的方法。我将强调 Webpack/Babel 方式,您可以利用 module-resolver 包来配置目录别名。你可以使用 .babelrcmodule-resolver 来配置你的外部依赖项,如下所示:

{
  "presets": ["@babel/preset-react"]
  "plugins": [
    "@babel/plugin-transform-runtime",
    [
      "module-resolver",
      {
        "root": ["./"],
        "extensions": [".js", ".jsx", ".ts", ".tsx"],
        "stripExtensions": [".js", ".jsx", ".ts", ".tsx"],
        "alias": {
          "@components": "./components",
          "@screens": "./screens",
          "@utils": "./utils",
          "@data": "./data",
          "@assets": "./assets",
          "@app": "./",
          "@myprojectlib": "../MyProject-Library",
          "@myprojecti18n": "../MyProject-Library/i18n/rn",
          "@myprojectbackend": "../MyProject-Backend/firebase/functions",
        }
      }
    ]
  ]
}

之后,你可以使用类似以下内容:

import { PictureFrame } from '@myprojectlib/components/PictureFrame';

并且你将在每个已配置为可以访问 "@myprojectlib": "../MyProject-Library" 的项目中使用它。

通过这种方式,你可以在项目内拥有多个版本的 React 和任何包,因为每个项目都将是一个独立的代码库,具有自己的依赖项但具有相同的共享组件/功能。

Christos Lytras
2020-01-26

正如 React.js 文档 中所述,


React Hooks:

Hooks 本质上是为了在无类组件中访问状态和其他 React 功能而构建的。

使用钩子时要牢记的规则

  1. 仅在顶层调用钩子

  2. 仅从 React 函数调用钩子

从您发布的详细信息来看,我相信您已经考虑到了上述几点。

对于根本原因分析,您可能需要在代码中设置一些调试语句或在控制台上记录消息以调查组件生命周期。


此外,如果您可以按如下方式重建/重新排列应用程序,效果会更好:

public/index.html

<body>
<div id="root" class="main-app"></div>
<script>
<!-- scrpits can be kept here -->
</script>
</body>

创建另一个组件 Main App 并在其中包含 sub-app-1 sub-app-2

并且由于您正在使用 "react-dom": "^16.12.0""react-router-dom": "^5.1.2" , 这样设置路由会更简单。


不能说这真的能解决你的情况,但纳入这是一个很好的做法。

祝一切顺利。

Ronnie
2020-01-25

我在我的项目中遇到了同样的问题,解决方案很简单。

您可以转到所有子项目并运行以下命令。

npm link ../main_app/node_modules/react

参考: https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react

patelmayankce
2020-01-26