在Vite+React+Astro+Next.js Monorepo中useState报错原因排查
Monorepo中React组件跨Workspace引用Hook调用错误排查
背景与架构
搭建了包含多个npm workspace的monorepo,结构如下:
- package - 待导出的JSX组件 - vite配置 - package.json - react - package.json - ... - next(基于React) - ... - astro(基于React) - ... - ...
通过npm workspace配置,在react、next、astro项目中引入package里的React组件。
相关配置
子项目(react/next/astro)的package.json配置
"workspaces": [ "../package" ], "dependencies": { "package": "1.0.1", ... "react": "^18.2.0", "react-dom": "^18.2.0" }
package组件库的package.json配置
{ "name": "package", "version": "1.0.1", "description": "", "type": "module", "main": "./dist/components.bundle.min.js", "module": "./dist/components.bundle.min.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "node --max_old_space_size=16384 ./node_modules/vite/bin/vite.js build" }, "exports": { ".": { "import": "./dist/components.bundle.min.js" } }, "author": "", "license": "ISC", "devDependencies": { "@vitejs/plugin-react": "^4.0.3", "terser": "^5.19.0" }, "dependencies": { "@babel/plugin-transform-runtime": "^7.22.9", "@rollup/plugin-babel": "^6.0.3", "@vitejs/plugin-legacy": "^4.1.0", "rollup-plugin-node-externals": "^6.1.1", "vite": "^4.4.4" }, "peerDependencies": { "react": "18.2.0", "react-dom": "18.2.0" }, "browserslist": [ "> 0.5%", "not dead" ] }
Vite打包配置(vite.config.js)
import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import { babel } from "@rollup/plugin-babel"; export default defineConfig({ plugins: [ react(), babel({ babelHelpers: "runtime", extensions: [".js", ".jsx", ".es6", ".es", ".mjs", "ts"], }), ], esbuild: { jsxFactory: "React.createElement", jsxFragment: "React.Fragment", }, build: { minify: false, lib: { entry: "./components/TestComponent.jsx", name: "jsxvideotest", fileName: "components.bundle.min", }, rollupOptions: { external: ["react"], output: { globals: { react: "React", }, }, }, }, resolve: { dedupe: ["react", "react-dom"], } });
错误现象
执行npm run build完成组件库打包后,react和next项目编译失败:
- Firefox报错:
Uncaught TypeError: dispatcher is null - Chrome报错:
Uncaught TypeError: Cannot read properties of null (reading 'useState')
同时伴随警告:Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. ...
而astro项目引入组件后无任何异常。
问题原因
核心原因是React多实例冲突:
- 组件库和子项目可能加载了不同的React实例,导致Hook调用时无法找到正确的React上下文(dispatcher)。
- Vite配置中external未包含
react-dom,可能导致打包时混入部分React依赖,和子项目的React实例冲突。 - 组件库的peerDependencies使用严格版本
18.2.0,若子项目React版本为^18.2.0(兼容版本),可能触发npm重复安装React。 - 额外配置的babel插件与
@vitejs/plugin-react产生冲突,导致JSX或Hook的编译逻辑异常。
解决方案
1. 修正组件库的peerDependencies配置
将严格版本改为兼容版本,并添加peerDependenciesMeta确保宿主环境提供依赖:
"peerDependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" }, "peerDependenciesMeta": { "react": { "optional": false }, "react-dom": { "optional": false } }
2. 优化Vite打包配置
- 移除多余的babel插件(
@vitejs/plugin-react已内置babel处理) - 将
react-dom加入external列表,确保不打包进组件库 - 移除冗余的esbuild jsx配置(
@vitejs/plugin-react自动处理)
修改后的vite.config.js:
import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], build: { minify: false, lib: { entry: "./components/TestComponent.jsx", name: "jsxvideotest", fileName: "components.bundle.min", }, rollupOptions: { external: ["react", "react-dom"], output: { globals: { react: "React", reactDom: "ReactDOM", }, }, }, }, resolve: { dedupe: ["react", "react-dom"], } });
3. 确保monorepo依赖统一
- 执行
npm install时,确保根目录node_modules中只有一份React和react-dom依赖(npm workspaces默认会提升依赖) - 检查子项目的node_modules,避免单独安装React(若存在,删除后重新执行
npm install)
4. 子项目的resolve配置补充
在react和next项目的配置中添加React dedupe:
- React项目(vite.config.js):
export default defineConfig({ resolve: { dedupe: ["react", "react-dom"], } });
- Next项目(next.config.js):
module.exports = { webpack: (config) => { config.resolve.dedupe = ['react', 'react-dom']; return config; } };
为什么Astro项目无异常?
Astro的默认构建流程会自动处理依赖去重,且其模块解析机制会强制使用同一版本的React,避免了多实例冲突。
内容的提问来源于stack exchange,提问作者Bach
相关产品推荐
相关产品推荐

