微前端 Invalid hook call?先把「window 注入 + externals」整明白

如果你做 qiankun / 微前端,遇到过下面任意一种情况,可以往下读:

  • 一用 useStateuseEffect 就报 Invalid hook call
  • 控制台 React is not defined ,或 antd is not defined
  • 子应用单独跑没问题,挂到基座里就白屏、Hooks 全炸
  • 不确定 webpack 的 externals 和基座的 window 注入 到底啥关系

PS: 我们项目(qiankun 基座 + 多个子应用 monorepo)已经按这套方案跑通了

你照这个思路配新子应用、或排查报错,应该是可以用上哒

🤔 首先呢,你先搞懂:到底出啥事了?

微前端 = 一个主应用(基座) + 好几个独立小应用(子应用),比如会话一个包、地图一个包、监控一个包,用 qiankun 拼在同一个页面里。

每个子应用代码里都会写:

typescript 复制代码
import React from 'react'
import { Button } from 'antd'

问题来了:如果每个子应用打包时,都把 React 打进了自己的 js 文件 ,浏览器里就会同时存在 好几份 React

打个比方(很重要)

React 有点像 全班共用的订书机

  • 正确做法:教室门口放 一台 订书机(基座加载一份 React),每个同学(子应用)用完放回去,大家用的是 同一台

  • 错误做法:每个同学书包里各带一台(每个子应用 bundle 里各一份 React)。
    看起来都能用,但 React 的规则是:全页面只能有一台「正版」
    你用它自带的 Hooks(useState 之类),它发现「这不是我这台机器上的调用」,就报错:

    Invalid hook call. Hooks can only be called inside of the body of a function component.

页面可能还能渲染一点,一用 Hooks 就炸。这就是开头说的那个坑。

「window.React 和子应用里的 React 不是同一个源头」是啥意思?

浏览器里有个全局对象 window,可以挂东西,比如 window.React

  • 基座 先加载,把真正的 React 挂上去:window.React = React
  • 子应用 如果打包时又 import 了一份 React,它用的是 自己 bundle 里那份 ,不是 window 上那份

两份 React 互不认识 → Hooks 报错。

DevTools 里你能看到 window.React 有值,但组件里用的可能是另一份。

💡 解法就两板斧(缺一刀都不行)

步骤 谁做 干啥 人话
第一斧 基座(主应用) registerGlobal() 把 React、antd 等 放到 window,全站共用这一份
第二斧 每个子应用 webpack externals 打包时 别再打 React 进 bundle ,要用就去 window.React

缺第一斧:子应用 externals 配置了 'React',但 window 上没有 → React is not defined

缺第二斧:window 上有 React,子应用还是自己打了一份 → 双 React,Invalid hook call

两板斧一起上,全站只有一份 React,大家都用这一份。

🎬 完整流程(按时间顺序看一遍)

复制代码
1. 用户打开网站
2. 基座 主应用 的 js 先执行
3. registerGlobal() 运行 → window.React = React,window.antd = antd ...
4. qiankun start,按需加载子应用 A 的 js
5. 子应用 A 里 import React
   → 因为 externals,不会打包 React
   → 实际读的是 window.React(和基座同一份)
6. 子应用 A 里 useState() → 正常,因为用的是正版那一台订书机

第一斧:基座 registerGlobal(把菜炒好放窗口)

基座代码里 主动 import ,再 挂到 window

typescript 复制代码
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import * as antd from 'antd'

const registerGlobal = () => {
  // 挂到 window 上,键名下面 externals 要对上
  window.React = React
  window.ReactDOM = ReactDOM
  window.antd = antd
  // mapboxgl、axios、moment 等同理
}

// ⚠️ 必须在 qiankun start() 之前调用!
registerGlobal()
start()

为啥叫 inject(注入)?

就是「基座先把公共库塞进 window,子应用来取」。

npm 包名 vs window 上的名字(要对表)

子应用 webpack 里写的是 window 上的名字,不是 npm 包名:

你 import 的包 window 上挂的名字 externals 里写
react React 'React'
react-dom ReactDOM 'ReactDOM'
antd antd 'antd'
mapbox-gl mapboxgl 'mapboxgl'

三个字对不齐,子应用就找不到 React。

第二斧:子应用 externals(打包时别带 React)

子应用 webpack.config.js

javascript 复制代码
module.exports = {
  externals: {
    react: 'React',           // import react → 用 window.React
    'react-dom': 'ReactDOM',
    antd: 'antd',
  },
}

意思是:构建子应用 js 时,遇到 import ... from 'react',不要打进 bundle,运行时用全局变量 React

打包成功后,子应用的 js 体积会小很多 ,里面也 不应该 再有一整份 react.production.min.js

🎭 还是食堂:串起来记

  • 基座 = 食堂,做好一大锅 React,放在取餐口(window
  • 子应用 = 各班组,只带自己的菜(业务代码) ,React 这种公共的 去窗口打
  • externals = 子应用打包规则:「React 别自己带,去窗口拿」

你既不在窗口放饭(没 registerGlobal),又规定去窗口拿(externals)→ 饿肚子。

窗口有饭,子应用又自己带一份 → 两台订书机,Hooks 报错。

🤯 进阶:为啥 antd 有时不能 external?

大多数页面:import { Button } from 'antd' → 用 window.antd,OK。

但某个 内部表单库 自己打包时已经塞进了一份 antd,你再 external,会出现 两份 antd,按钮样式、弹层行为可能对不上。

所以有的项目 externals 写成 函数 :在特定包 context 里 不让 antd external ,让它自己带;其它业务代码仍用 window.antd

这是踩坑后的补丁,第一版先搞懂 React 双实例再说 antd。

🔍 确认配置是否正确

  1. Network:基座加载了 react 相关 js;子应用 chunk 明显更小
  2. 报错没了useState 不再 Invalid hook call
  3. 构建产物:搜一下子应用打包结果,不应出现完整 react 生产环境源码字符串

🕳️ 报错对照

报错 通常是因为
React is not defined 基座没 registerGlobal,或子应用比基座先跑
Invalid hook call 双 React------externals 没生效,子应用仍打了自己的 React
antd is not defined window 上没挂 antd,或名字写错

🎬 最后说两句

搞懂「一份 React 全站共用」,后面改 webpack、升级 React 版本才有底

相关推荐
宿6741 小时前
vue3-DOM树
前端·javascript·vue.js
白狐_7982 小时前
408数据结构:中缀转后缀与操作符栈容量——真题精讲
前端·javascript·数据结构
谷哥的小弟2 小时前
TypeScript在Vue3中的常见写法
前端·javascript·typescript
人间凡尔赛2 小时前
2026 前端全栈新范式:Server Actions + Edge Runtime,告别传统 API 路由
前端·全栈·next.js
小月土星3 小时前
useCallback 深度解析:React 性能优化的核心利器
react.js·前端框架
码林鼠5 小时前
2026前端面试题(二)
前端
满栀5856 小时前
请求拦截、响应拦截、业务错误统一处理
前端·typescript·anti-design-vue
宿6747 小时前
tsconfig.node.json
前端·javascript·vue.js
小杨互联网7 小时前
Cursor 前端 Dist 自动化逆向框架:/fr 流水线 · 4 脚本 · Vite/Webpack
前端·webpack·自动化·前端逆向框架·ai前端逆向框架