Material UI 从 v3 升级到 v4:依赖更新与破坏性变更完整迁移指南
本篇指南覆盖 Material UI(MUI)v3 到 v4 的全部破坏性变更,按"更新依赖 → Core/Styles/Theme 全局变化 → 各组件逐项 API 调整"的顺序组织迁移路径。读完本文后,你可以对照每一节完成升级,并借助仓库中的官方 codemod 自动化脚本与源码实现,理解每项变更背后的设计意图(如全面转向 React.forwardRef、JSS v10 升级、theme.spacing 函数化 API),从而把站点平滑迁移到 v4。
背景:为什么要迁移
本文档聚焦如何(how)从 v3 迁移到 v4。关于为什么要升级到 v4 的产品层面的考量,官方在发布博客中有专门阐述。对开发者而言,迁移的直接收益包括:所有组件统一转发 ref、更贴近 Web 标准的类名与 CSS 结构、theme.spacing 等更符合直觉的新 API,以及 TypeScript 类型层面的规范化。
第一步:更新依赖
升级 Material UI 核心包
首先更新 package.json,将核心包切换到 v4:
"dependencies": {
"@material-ui/core": "^4.0.0"
}
或者执行安装命令:
npm install @material-ui/core
# 或
yarn add @material-ui/core
升级 React 最低版本
v4 将 React 最低要求版本从 react@^16.3.0 提升到 react@^16.8.0。这个提升的目的是让组件库可以全面依赖 Hooks——v4 的组件实现不再使用 class API。如果你的项目还在使用 React 16.3~16.7,需要先升级 React 才能安装 @material-ui/core@^4.0.0。
升级 Material UI Styles
如果 v3 时代你使用了 @material-ui/styles(例如使用 withStyles、withTheme、StylesProvider 等函数式组件),需要同步升级到 v4:
"dependencies": {
"@material-ui/styles": "^4.0.0"
}
或者执行:
npm install @material-ui/styles
# 或
yarn add @material-ui/styles
全局性破坏性变更(Core)
所有组件现在都会转发 ref。 v4 内部统一改用 React.forwardRef() 实现,这意味着内部组件树和 displayName 都发生了变化,依赖组件内部结构的 shallow 测试或快照测试可能会失败,需要相应调整断言。
与 ref 相关的另一项变化是 innerRef 的语义:在 v3 中,innerRef 返回的是组件实例(若内部是函数组件则返回 undefined);在 v4 中,它统一返回根 DOM 节点的 ref。各组件的 API 文档中会列出其对应的根组件(root component),可用于确认 ref 最终落在哪个 DOM 元素上。
Styles 层的破坏性变更
JSS v10 不向后兼容 v9
⚠️ Material UI v4 依赖 JSS v10,而 JSS v10 与 v9 不向后兼容。请确保环境中没有安装 JSS v9——从 package.json 中移除 react-jss 通常能解决这个问题。同时,StylesProvider 组件取代了原先的 JssProvider。
移除 withTheme() 的第一个参数
withTheme() 不再接受第一个参数(该参数原本是为未来可能的选项预留的占位符,最终从未启用)。新的调用方式与 emotion、styled-components 的 API 保持一致:
-const DeepChild = withTheme()(DeepChildRaw);
+const DeepChild = withTheme(DeepChildRaw);
convertHexToRGB 更名为 hexToRgb
颜色处理工具函数重命名并调整了导入路径:
-import { convertHexToRgb } from '@material-ui/core/styles/colorManipulator';
+import { hexToRgb } from '@material-ui/core/styles';
keyframes API 作用域化
为了让动画逻辑相互隔离,@keyframes 的使用被限定在作用域内。需要把动画名称加上 $ 前缀:
rippleVisible: {
opacity: 0.3,
- animation: 'mui-ripple-enter 100ms cubic-bezier(0.4, 0, 0.2, 1)',
+ animation: '$mui-ripple-enter 100ms cubic-bezier(0.4, 0, 0.2, 1)',
},
'@keyframes mui-ripple-enter': {
'0%': {
opacity: 0.1,
},
'100%': {
opacity: 0.3,
},
},
这一变更对应 JSS v10 的 keyframes 语法,$ 前缀表示该 keyframes 是局部作用域的,可帮助隔离各组件的动画定义。
Theme 层的破坏性变更
theme.palette.augmentColor() 不再产生副作用
v4 中 augmentColor() 不再修改传入的颜色对象,必须使用其返回值:
-const background = { main: color };
-theme.palette.augmentColor(background);
+const background = theme.palette.augmentColor({ main: color });
console.log({ background });
这一点在当前仓库源码中可以得到印证:createPalette 实现 中,augmentColor 是一个纯函数,接收 { color, name, mainShade, lightShade, darkShade } 参数并返回补全后的 PaletteColor 对象,供 primary、secondary、error、warning、info、success 等色板的构建复用,不对入参做任何原地修改。类型声明 也明确了其签名 augmentColor: (options: PaletteAugmentColorOptions) => PaletteColor。
移除 useNextVariants
typography.useNextVariants: true 选项在 v4 中已移除——"next variants" 已成为默认行为,可以安全地从主题创建代码中删掉:
typography: {
- useNextVariants: true,
},
theme.spacing.unit 废弃,改用函数式 API
theme.spacing.unit 用法被废弃,新 API 把 spacing 当作函数调用:
label: {
[theme.breakpoints.up('sm')]: {
- paddingTop: theme.spacing.unit * 12,
+ paddingTop: theme.spacing(12),
},
}
提示:可以传多个参数,theme.spacing(1, 2) 等价于 '8px 16px'。
这一项可以借助仓库内置的官方 codemod 自动完成。在 mui-codemod 使用文档 中,theme-spacing-api 迁移脚本会把 theme.spacing.unit x 形式批量改写为 theme.spacing(x):
npx @mui/codemod@latest v4.0.0/theme-spacing-api <path>
转换规则示例(来自 README):
-const spacing = theme.spacing.unit;
+const spacing = theme.spacing(1);
-const spacing = theme.spacing.unit / 5;
+const spacing = theme.spacing(0.2);
-const spacing = theme.spacing.unit * 5 * 5;
+const spacing = theme.spacing(5) * 5;
对应的测试用例见 theme-spacing-api.test.js,覆盖了整数、分数、乘法链等多种 unit 运算形式的转换。
Layout 层的破坏性变更
Grid 的 spacing API 重构
为了支持任意间距值、并去掉"心里默数 8 的倍数"的心智负担,Grid 容器的 spacing 从像素值改为倍数(multiplier):
/**
* Defines the space between the type `item` component.
* It can only be used on a type `container` component.
*/
- spacing: PropTypes.oneOf([0, 8, 16, 24, 32, 40]),
+ spacing: PropTypes.oneOf([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
后续可以通过主题实现自定义的 Grid spacing 转换函数(见文档站 System/Spacing 的 Transformation 章节)。
Container 从 @material-ui/lab 移入 @material-ui/core
-import Container from '@material-ui/lab/Container';
+import Container from '@material-ui/core/Container';
Slider 从 @material-ui/lab 移入 @material-ui/core
-import Slider from '@material-ui/lab/Slider'
+import Slider from '@material-ui/core/Slider'
TypeScript 破坏性变更
输入类组件的 value 类型规范化为 unknown
输入组件的 value prop 类型统一规范化为 unknown,影响 InputBase、NativeSelect、OutlinedInput、Radio、RadioGroup、Select、SelectInput、Switch、TextArea 和 TextField。事件处理器需要调整类型:
function MySelect({ children }) {
- const handleChange = (event: any, value: string) => {
+ const handleChange = (event: any, value: unknown) => {
// handle value
};
return <Select onChange={handleChange}>{children}</Select>
}
该变更的详细处理策略(value 与事件处理器)在仓库的 TypeScript 指南中有专节说明,见 TypeScript 指南。
各组件的逐项变更
Button
移除已废弃的按钮变体(flat、raised、fab):
-<Button variant="raised" />
+<Button variant="contained" />
-<Button variant="flat" />
+<Button variant="text" />
-import Button from '@material-ui/core/Button';
-<Button variant="fab" />
+import Fab from '@material-ui/core/Fab';
+<Fab />
-import Button from '@material-ui/core/Button';
-<Button variant="extendedFab" />
+import Fab from '@material-ui/core/Fab';
+<Fab variant="extended" />
此外,ButtonBase 的 component prop 传入的组件必须能够持有 ref。文档站的 composition 指南(含 "Caveat with refs" 小节)给出了迁移策略。此约束同样适用于 BottomNavigationAction、Button、CardActionArea、Checkbox、ExpansionPanelSummary、Fab、IconButton、MenuItem、Radio、StepButton、Tab、TableSortLabel,以及 button prop 为 true 时的 ListItem。
Card
CardActions:disableActionSpacingprop 更名为disableSpacing;CardActions:移除disableActionSpacingCSS 类;CardActions:actionCSS 类更名为spacing。
ClickAwayListener
隐藏(不再透出)react-event-listener 的 props,属于内部实现细节的收敛。
Dialog
DialogActions:disableActionSpacingprop 更名为disableSpacing;DialogActions:actionCSS 类更名为spacing;DialogContentText:改用 typography 变体body1,替代原先的subtitle1;Dialog:子元素必须能持有 ref,迁移策略参考 composition 指南。
Divider
移除已废弃的 inset prop,改用 variant:
-<Divider inset />
+<Divider variant="inset" />
ExpansionPanel
ExpansionPanelActions:actionCSS 类更名为spacing;ExpansionPanel:提升disabled和expanded样式规则的 CSS 优先级(specificity);ExpansionPanel:CollapsePropsprop 更名为TransitionProps。
List
- 列表组件按设计规范重新整理:使用头像时,必须通过
ListItemAvatar组件包裹;使用左侧复选框时,必须通过ListItemIcon包裹;图标按钮应设置edge属性; List:dense不再减少List元素自身的上下 padding;ListItem:提升disabled和focusVisible样式规则的 CSS 优先级。
Menu
MenuItem:移除了固定高度。高度现在由浏览器根据 padding 与 line-height 计算得出,内容较长时可自然撑开。
Modal
- 子元素必须能持有 ref(同样适用于
Dialog和Popover),迁移策略参考 composition 指南; - 移除了 Modal 的 classes 定制 API——官方注明独立使用时可带来 74% 的包体积缩减;
event.defaultPrevented现在被忽略:即使对 keydown Escape 事件调用了event.preventDefault(),Modal 也会关闭。原因是preventDefault()的本意是阻止浏览器默认行为(如点击复选框使其选中、点按钮提交表单、左移光标等),只有特殊 HTML 元素才有这些默认行为;如果你不想触发 Modal 的onClose,应改用event.stopPropagation()。
Paper
降低默认 elevation,使 Paper 的默认阴影与 Card、ExpansionPanel 保持一致(均为 elevation 2)。若要保持 v3 的默认外观,需要显式指定:
-<Paper />
+<Paper elevation={2} />
该变更同样影响 ExpansionPanel(其内部由 Paper 承载)。
Portal 与 Slide
Portal:使用disablePortal时,子元素必须能持有 ref;Slide:子元素必须能持有 ref。
两者均参考 composition 指南中的 ref 迁移策略。
Switch
重构实现以便样式覆写更容易,类名按设计规范措辞重命名:
-icon
-bar
+thumb
+track
Snackbar
对齐新设计规范:调整了尺寸,默认过渡动画从 Slide 改为 Grow。
SvgIcon
nativeColor 更名为 htmlColor。React 在处理 for 属性时采用同样的命名思路(对应 htmlFor),此变更遵循相同逻辑:
-<AddIcon nativeColor="#fff" />
+<AddIcon htmlColor="#fff" />
Tabs
Tab:移除labelContainer、label和labelWrapped三个 class key,同时移除了 2 个中间 DOM 层,DOM 结构更简单。原有针对这些层级的自定义样式应迁移到rootclass key 上;Tabs:移除已废弃的fullWidth和scrollableprops,改用variant:
-<Tabs fullWidth scrollable />
+<Tabs variant="scrollable" />
Table
TableCell:移除已废弃的numeric属性,改用align:
-<TableCell numeric>{row.calories}</TableCell>
+<TableCell align="right">{row.calories}</TableCell>
TableRow:移除固定高度 CSS 属性,行高由浏览器根据 padding 与 line-height 计算;TableCell:dense模式迁移到独立属性上:
-<TableCell padding="dense" />
+<TableCell size="small" />
TablePagination:不再尝试修正无效的(page、count、rowsPerPage)组合,而是抛出警告(warning),把数据一致性的责任交还给使用者。
TextField / Input
InputLabel:FormLabel组件的全部样式现在可以通过InputLabel的 CSS API 覆盖,FormLabelClasses属性被移除:
<InputLabel
- FormLabelClasses={{ asterisk: 'bar' }}
+ classes={{ asterisk: 'bar' }}
>
Foo
</InputLabel>
InputBase:默认 box-sizing 模型改为box-sizing: border-box;,这解决了fullWidthprop 相关的问题;InputBase:移除inputTypeclass。
Tooltip
- 子元素必须能持有 ref(参考 composition 指南);
- 显示时机变更:仅在 focus-visible(键盘焦点)时出现,不再对任意 focus 生效——这改善了鼠标用户不会误触 Tooltip 的体验。
Typography
- 移除已废弃的 typography 变体,按下表替换:
| v3 变体 | v4 替换 |
|---|---|
display4 |
h1 |
display3 |
h2 |
display2 |
h3 |
display1 |
h4 |
headline |
h5 |
title |
h6 |
subheading |
subtitle1 |
body2 |
body1 |
body1(默认) |
body2(默认) |
- 移除带主观判断的
display: block默认样式,新增display?: 'initial' | 'inline' | 'block'属性显式控制; headlineMapping属性更名为variantMapping,以更贴合其用途:
-<Typography headlineMapping={headlineMapping}>
+<Typography variantMapping={variantMapping}>
- 默认变体从
body2改为body1:16px 比 14px 是更合理的默认字号(Bootstrap、material.io 甚至官方文档本身都使用 16px 作为默认字号); - 移除 typography 变体的默认颜色——颜色在大多数场景下应当继承,这正是 Web 的默认行为;
color="default"更名为color="initial":default一词语义模糊,应避免使用。
运行环境与 UMD
Node
v4 放弃了 Node 6 支持(Node 6 已进入 EOL),建议升级到 Node 8 或更高版本。
UMD 全局变量更名
UMD 构建的全局变量名调整,便于与 CDN 配合使用:
const {
Button,
TextField,
-} = window['material-ui'];
+} = MaterialUI;
命名与其他 React 生态项目保持一致:material-ui => MaterialUI、react-dom => ReactDOM、prop-types => PropTypes。
迁移执行建议
- 先升依赖:将 React 升到
^16.8.0以上,再安装@material-ui/core@^4.0.0(如用到样式函数式组件则连同@material-ui/styles@^4.0.0),并清理环境中可能残留的 JSS v9 /react-jss; - 跑自动化 codemod:对
theme.spacing.unit这类可机械替换的模式,使用 mui-codemod 提供的v4.0.0/theme-spacing-api等脚本批量处理,再人工复核 diff; - 按本文组件小节逐项检查:重点排查 ref 相关约束(
Modal、Dialog、Tooltip、Slide、ButtonBase的component等)、类名重命名(CardActions/DialogActions/ExpansionPanelActions的action → spacing、Switch的icon/bar → thumb/track)以及 Typography 变体映射; - 修复测试:由于全部组件改用
React.forwardRef,组件树与 display name 的变化可能使 shallow 测试和快照测试失效,需要更新断言; - 视觉回归:
Paper默认 elevation、Snackbar尺寸与过渡动画、Typography默认字号与颜色继承等外观变化,建议对关键页面做一次视觉走查,必要时用elevation、display等显式属性恢复原外观。
完成以上步骤后,即可在 v4 上获得统一转发 ref 的组件体系、规范化后的 Web 标准类名,以及 theme.spacing(x) 这类更直观的 API。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00