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

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

  • 一用 useState、useEffect 就报 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 版本才有底

相关推荐
Cosolar9 小时前
不收费、不加水印、数据归你:我做了个开源的在线简历工具
后端·面试·前端框架
米米商聊10 小时前
同名附件为什么被 Map 覆盖?重复键与一对多索引
前端·javascript
echome88811 小时前
Python 异常处理:try、else、finally 的正确搭配
开发语言·前端·python·异常处理·编程技巧
时间的情敌11 小时前
OpenLayers 实战|Vue3 + OpenLayers 完整工程案例(加载天地图、要素、弹窗、绘制、WFS)
前端·openlayers
陈随易11 小时前
下载神器!Motrix 停更三年后重写回归
前端·后端·程序员
前端的日常12 小时前
一个人+AI做情侣食谱小程序,0 费用的服务器
前端·后端
计算机魔术师12 小时前
谷歌搜索广告踩刹车,AI广告成为救命稻草
前端
总要学点什么12 小时前
第81篇:数据可视化-PM2.5分布——让圆点的胖瘦替每座城市报出浓度
前端·信息可视化·echarts·cesium·空间分析
IT_陈寒12 小时前
Vue的v-for里用了个临时变量,页面直接挂了
前端·人工智能·后端
hunter_07613 小时前
一次 iOS 真机签名 409 的完整排查:证书、设备、描述文件为什么总是"对不上"
前端