项目中存在多React实例 如何解决?

基于Yarn workspace & lerna 的项目中 报错 Invalid hook call. Hooks can only be called inside of the body of a function component.

复现过程:
yarn start
http://localhost:3000/sub-app
报错信息:
Uncaught 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://fb.me/react-invalid-h... for tips about how to debug and fix this problem.

项目地址 bug-demo

项目结构
mono-repo
-packages
-container
-subApp

在container项目中引入subApp打包后的文件 路由/sub-app 下 当引入的是一个hooks组件就会报错 而路由到/sub-app/foo 由于是class组件所以没有问题

部分关键代码

// /packages/container/src/App.js
import React from 'react'
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom';
import About from './About';
import Header from './Header';
import Loadable from 'react-loadable'

const Logo = () => {
  return <div>LOGO</div>
}

const loadableComponent = (loader) => {
  return Loadable({
    loader: () =>
      loader().then(
        (res) => {
          return res
        },
        (e) => () => {
          console.log(e)
        }
      ),
    loading() {
      return (
        <div>loading</div>
      )
    }
  })
}

const loadSubApp = (subAppInfo) => {
  const { name, host } = subAppInfo
  return new Promise((resolve, reject)=> {
    fetch(`${host}/${name}/asset-manifest.json`)
      .then(res => res.json())
      .then(manifest => {
        const script = document.createElement('script');
        script.src = `${host}${manifest.files['main.js']}`;
        const timeout = setTimeout(()=>{
          console.error(`MicroApp ${name} timeout`);
          reject(new Error(`MicroApp ${name} timeout`));
        },20000)
        script.onload = () => {
          clearTimeout(timeout)
          const app = window[name]
          console.log({app, name})
          console.log(`MicroApp ${name} loaded success`);
          resolve(app)
        }
        script.onerror = (e) => {
          clearTimeout(timeout);
          console.error(`MicroApp ${name} loaded error`, e);
          reject(e)
        }
        document.body.appendChild(script);
      })
  })
}

const subLoader = (name) => async () => {
  const App = await loadSubApp({ name: 'subApp', host: process.env.REACT_APP_SUBAPP_HOST })
  console.log({App, window})
  return App[name]
}



const App = () => {
  return (
    <BrowserRouter>
      <React.Fragment>
        <Logo />
        <ul>
          <li>
          <Link to="/">Home</Link>
          </li>
          <li>
          <Link to="/header">header</Link>
          </li>
          <li>
          <Link to="/sub-app">subApp</Link>
          </li>
          <li>
          <Link to="/sub-app/foo">subApp foo</Link>
          </li>
          
        </ul>
        <Switch>
          <Route exact path="/" component={About} />
          <Route exact path="/header" render={() => <Header /> }/>
          <Route exact path="/sub-app/foo" render={()=> {
            const RenderSubApp = loadableComponent(subLoader('Foo'))
            return <RenderSubApp />
          }}/>
          <Route exact path="/sub-app" render={()=> {
            const RenderSubApp = loadableComponent(subLoader('Count'))
            return <RenderSubApp />
          }}/>
        </Switch>
      </React.Fragment>
    </BrowserRouter>
  )
}

export default App
// /packages/subApp/config-overrides.js
const {
  override,
  // addWebpackAlias
} = require("customize-cra");
// const path = require("path");

const dropConsole = () => {
  return (config) => {

    if (config.optimization.minimizer) {
      config.optimization.minimizer.forEach((minimizer) => {
        if (minimizer.constructor.name === "TerserPlugin") {
          minimizer.options.terserOptions.compress.drop_console = true;
        }
      });
    }
    return config;
  };
};

const optBuild= () => config => {
  config.optimization.runtimeChunk = false;
  config.optimization.splitChunks = {
    cacheGroups: {
      default: false,
    },
  }; 
  return config
}

const disableSourceMap = () => (config) => {
  if (process.env.NODE_ENV === "production") {
    config.devtool = false;
  }
  return config;
};

const customizeCraOverride = override(
  disableSourceMap(),
  dropConsole(),
  optBuild(),
  // addWebpackAlias({
  //   'react': path.resolve(__dirname, '../container/node_modules/react'),
  //   'react-dom': path.resolve(__dirname, '../container/node_modules/react-dom')
  // })
);

const webpack = (config, env) => {
  const webpackConfig = customizeCraOverride(config, env);

  return {
    ...webpackConfig,
    output: {
      ...webpackConfig.output,
      library: "subApp",
      libraryTarget: 'window',
    },
    // externals: {
    //   'react': 'react',
    //   'react-dom': 'react-dom'
    // },
  };
};

module.exports = {
  webpack,
};

如下这些方法都试过了 不起作用

阅读 3.1k
1 个回答

在子应用的 package.json 文件里,设置 peerDependencies:

"peerDependencies": {
  "react": "^17.0.2",
  "react-dom": "^17.0.2"
}

然后在子应用的 webpack 配置里:

externals: {
  'react': 'React',
  'react-dom': 'ReactDOM'
}

然后在主应用里,要在加载子应用之前,把 React 和 ReactDOM 弄到全局作用域里:

window.React = React;
window.ReactDOM = ReactDOM;

子应用和主应用的版本react要一样

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题