yarn workspaces 和 lerna 导致 Invalid Hook call
yarn workspaces and lerna cause Invalid Hook call
我认为这是一个重复的 React 问题
当前行为:
在 container
项目中,当我路由到 sub-app/foo
时它工作正常。但是当我路由到 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:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
注意:sub-app/foo
将得到一个 class 组件。 sub-app
将获得一个 React hooks 组件
预期行为:我期望的结果是 subApp
代码在 container
中可以正常工作
项目结构:
/mono-repo
packages
- container
- subApp
// /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>
)
}
// /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,
};
项目linkbug-demo
这些解决方案对我不起作用
- https://reactjs.org/warnings/invalid-hook-call-warning.html
- https://github.com/facebook/react/issues/13991
- https://github.com/facebook/react/issues/15315
- Yarn Workspaces and Invalid Hook call
我不确定这是否能完全解决您的问题,但错误消失并且路由正常工作。没有任何加载,我很确定我没有正确的环境变量。但是,基本上您的 /packages/container/src/App.js
文件中只有一些错误的路由器代码。我克隆了存储库,这就是我将其更改为:
<Switch>
<Route exact path="/" component={About} />
<Route exact path="/header" render={() => <Header /> }/>
<Route exact path="/sub-app" render={()=> {
const RenderSubApp = loadableComponent(subLoader('Count'))
return <RenderSubApp />
}}>
<Route exact path="/foo" render={()=> {
const RenderSubApp = loadableComponent(subLoader('Foo'))
return <RenderSubApp />
}}/>
</Route>
</Switch>
注意 /foo
路由如何成为 /sub-app
路由的子路由。虽然错误消失了,但 React 仍然显示一条警告消息:Warning: You should not use <Route render> and <Route children> in the same route; <Route render> will be ignored
。我不确定 lerna 是如何工作的,但可能有一些解决方法。
查看 react-router 文档的 this section。
终于解决了这个问题
// /packages/subApp/config-overrides.js
...
const webpack = (config, env) => {
const webpackConfig = customizeCraOverride(config, env);
return {
...webpackConfig,
output: {
...webpackConfig.output,
library: "subApp",
libraryTarget: 'window',
},
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
},
};
};
module.exports = {
webpack
};
// packages/container/src/index.js
...
export {
React,
ReactDOM
}
// packages/container/config-overrides.js
...
const webpack = (config, env) => {
const webpackConfig = customizeCraOverride(config, env);
return {
...webpackConfig,
output: {
...webpackConfig.output,
libraryTarget: 'umd',
},
}
};
module.exports = {
webpack
};
我认为这是一个重复的 React 问题
当前行为:
在 container
项目中,当我路由到 sub-app/foo
时它工作正常。但是当我路由到 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:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
注意:sub-app/foo
将得到一个 class 组件。 sub-app
将获得一个 React hooks 组件
预期行为:我期望的结果是 subApp
代码在 container
项目结构:
/mono-repo
packages
- container
- subApp
// /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>
)
}
// /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,
};
项目linkbug-demo
这些解决方案对我不起作用
- https://reactjs.org/warnings/invalid-hook-call-warning.html
- https://github.com/facebook/react/issues/13991
- https://github.com/facebook/react/issues/15315
- Yarn Workspaces and Invalid Hook call
我不确定这是否能完全解决您的问题,但错误消失并且路由正常工作。没有任何加载,我很确定我没有正确的环境变量。但是,基本上您的 /packages/container/src/App.js
文件中只有一些错误的路由器代码。我克隆了存储库,这就是我将其更改为:
<Switch>
<Route exact path="/" component={About} />
<Route exact path="/header" render={() => <Header /> }/>
<Route exact path="/sub-app" render={()=> {
const RenderSubApp = loadableComponent(subLoader('Count'))
return <RenderSubApp />
}}>
<Route exact path="/foo" render={()=> {
const RenderSubApp = loadableComponent(subLoader('Foo'))
return <RenderSubApp />
}}/>
</Route>
</Switch>
注意 /foo
路由如何成为 /sub-app
路由的子路由。虽然错误消失了,但 React 仍然显示一条警告消息:Warning: You should not use <Route render> and <Route children> in the same route; <Route render> will be ignored
。我不确定 lerna 是如何工作的,但可能有一些解决方法。
查看 react-router 文档的 this section。
终于解决了这个问题
// /packages/subApp/config-overrides.js
...
const webpack = (config, env) => {
const webpackConfig = customizeCraOverride(config, env);
return {
...webpackConfig,
output: {
...webpackConfig.output,
library: "subApp",
libraryTarget: 'window',
},
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
},
};
};
module.exports = {
webpack
};
// packages/container/src/index.js
...
export {
React,
ReactDOM
}
// packages/container/config-overrides.js
...
const webpack = (config, env) => {
const webpackConfig = customizeCraOverride(config, env);
return {
...webpackConfig,
output: {
...webpackConfig.output,
libraryTarget: 'umd',
},
}
};
module.exports = {
webpack
};