You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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多实例冲突:

  1. 组件库和子项目可能加载了不同的React实例,导致Hook调用时无法找到正确的React上下文(dispatcher)。
  2. Vite配置中external未包含react-dom,可能导致打包时混入部分React依赖,和子项目的React实例冲突。
  3. 组件库的peerDependencies使用严格版本18.2.0,若子项目React版本为^18.2.0(兼容版本),可能触发npm重复安装React。
  4. 额外配置的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 00:05:53