首页
/ Material UI 从 v3 升级到 v4:依赖更新与破坏性变更完整迁移指南

Material UI 从 v3 升级到 v4:依赖更新与破坏性变更完整迁移指南

2026-09-06 15:44:40作者:凤尚柏Louis

本篇指南覆盖 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(例如使用 withStyleswithThemeStylesProvider 等函数式组件),需要同步升级到 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 对象,供 primarysecondaryerrorwarninginfosuccess 等色板的构建复用,不对入参做任何原地修改。类型声明 也明确了其签名 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,影响 InputBaseNativeSelectOutlinedInputRadioRadioGroupSelectSelectInputSwitchTextAreaTextField。事件处理器需要调整类型:

 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

移除已废弃的按钮变体(flatraisedfab):

-<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" />

此外,ButtonBasecomponent prop 传入的组件必须能够持有 ref。文档站的 composition 指南(含 "Caveat with refs" 小节)给出了迁移策略。此约束同样适用于 BottomNavigationActionButtonCardActionAreaCheckboxExpansionPanelSummaryFabIconButtonMenuItemRadioStepButtonTabTableSortLabel,以及 button prop 为 true 时的 ListItem

Card

  • CardActionsdisableActionSpacing prop 更名为 disableSpacing
  • CardActions:移除 disableActionSpacing CSS 类;
  • CardActionsaction CSS 类更名为 spacing

ClickAwayListener

隐藏(不再透出)react-event-listener 的 props,属于内部实现细节的收敛。

Dialog

  • DialogActionsdisableActionSpacing prop 更名为 disableSpacing
  • DialogActionsaction CSS 类更名为 spacing
  • DialogContentText:改用 typography 变体 body1,替代原先的 subtitle1
  • Dialog:子元素必须能持有 ref,迁移策略参考 composition 指南。

Divider

移除已废弃的 inset prop,改用 variant

-<Divider inset />
+<Divider variant="inset" />

ExpansionPanel

  • ExpansionPanelActionsaction CSS 类更名为 spacing
  • ExpansionPanel:提升 disabledexpanded 样式规则的 CSS 优先级(specificity);
  • ExpansionPanelCollapseProps prop 更名为 TransitionProps

List

  • 列表组件按设计规范重新整理:使用头像时,必须通过 ListItemAvatar 组件包裹;使用左侧复选框时,必须通过 ListItemIcon 包裹;图标按钮应设置 edge 属性;
  • Listdense 不再减少 List 元素自身的上下 padding;
  • ListItem:提升 disabledfocusVisible 样式规则的 CSS 优先级。

Menu

  • MenuItem:移除了固定高度。高度现在由浏览器根据 padding 与 line-height 计算得出,内容较长时可自然撑开。

Modal

  • 子元素必须能持有 ref(同样适用于 DialogPopover),迁移策略参考 composition 指南;
  • 移除了 Modal 的 classes 定制 API——官方注明独立使用时可带来 74% 的包体积缩减;
  • event.defaultPrevented 现在被忽略:即使对 keydown Escape 事件调用了 event.preventDefault(),Modal 也会关闭。原因是 preventDefault() 的本意是阻止浏览器默认行为(如点击复选框使其选中、点按钮提交表单、左移光标等),只有特殊 HTML 元素才有这些默认行为;如果你不想触发 Modal 的 onClose,应改用 event.stopPropagation()

Paper

降低默认 elevation,使 Paper 的默认阴影与 CardExpansionPanel 保持一致(均为 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:移除 labelContainerlabellabelWrapped 三个 class key,同时移除了 2 个中间 DOM 层,DOM 结构更简单。原有针对这些层级的自定义样式应迁移到 root class key 上;
  • Tabs:移除已废弃的 fullWidthscrollable props,改用 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 计算;
  • TableCelldense 模式迁移到独立属性上:
-<TableCell padding="dense" />
+<TableCell size="small" />
  • TablePagination:不再尝试修正无效的(pagecountrowsPerPage)组合,而是抛出警告(warning),把数据一致性的责任交还给使用者。

TextField / Input

  • InputLabelFormLabel 组件的全部样式现在可以通过 InputLabel 的 CSS API 覆盖,FormLabelClasses 属性被移除:
 <InputLabel
-  FormLabelClasses={{ asterisk: 'bar' }}
+  classes={{ asterisk: 'bar' }}
 >
   Foo
 </InputLabel>
  • InputBase:默认 box-sizing 模型改为 box-sizing: border-box;,这解决了 fullWidth prop 相关的问题;
  • InputBase:移除 inputType class。

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 => MaterialUIreact-dom => ReactDOMprop-types => PropTypes

迁移执行建议

  1. 先升依赖:将 React 升到 ^16.8.0 以上,再安装 @material-ui/core@^4.0.0(如用到样式函数式组件则连同 @material-ui/styles@^4.0.0),并清理环境中可能残留的 JSS v9 / react-jss
  2. 跑自动化 codemod:对 theme.spacing.unit 这类可机械替换的模式,使用 mui-codemod 提供的 v4.0.0/theme-spacing-api 等脚本批量处理,再人工复核 diff;
  3. 按本文组件小节逐项检查:重点排查 ref 相关约束(ModalDialogTooltipSlideButtonBasecomponent 等)、类名重命名(CardActions/DialogActions/ExpansionPanelActionsaction → spacingSwitchicon/bar → thumb/track)以及 Typography 变体映射;
  4. 修复测试:由于全部组件改用 React.forwardRef,组件树与 display name 的变化可能使 shallow 测试和快照测试失效,需要更新断言;
  5. 视觉回归Paper 默认 elevation、Snackbar 尺寸与过渡动画、Typography 默认字号与颜色继承等外观变化,建议对关键页面做一次视觉走查,必要时用 elevationdisplay 等显式属性恢复原外观。

完成以上步骤后,即可在 v4 上获得统一转发 ref 的组件体系、规范化后的 Web 标准类名,以及 theme.spacing(x) 这类更直观的 API。

登录后查看全文
热门项目推荐
相关项目推荐