首页
/ Material UI v7 升级指南:从 v6 迁移的完整路径——包布局、Grid 改名、废弃 API 移除与 codemod 实操

Material UI v7 升级指南:从 v6 迁移的完整路径——包布局、Grid 改名、废弃 API 移除与 codemod 实操

2026-09-06 17:20:09作者:卓炯娓

本文基于 Material UI 仓库中官方的 v7 升级文档 upgrade-to-v7.md 展开,系统讲解从 Material UI v6 升级到 v7 的每一步:包布局(package exports)变更、Grid/Grid2 组件改名、InputLabel size 属性标准化、主题行为变化,以及 v5 起被废弃 API 的移除细节。读完后你将掌握:需要升级/保持不变的 npm 包清单、React 18 及以下的 react-is 版本对齐方案、所有破坏性变更的代码修改方式(diff 形式),以及如何用仓库内置的 @mui/codemod 自动完成大部分迁移工作。

说明:本仓库当前已演进到更新的版本(如 packages/mui-material/package.jsonversion 字段为 9.x),但 v6 → v7 的迁移要求仍以官方升级文档为准,本文即按该文档如实整理并结合仓库源码验证。

一、为什么要升级到 v7

1.1 改进的 ESM 支持

v7 更新了包的目录布局,通过 package.json 中的 exports 字段同时、无歧义地支持合法的 ESM 与 CommonJS 两种模块形态(exports 语义可参考 Node.js 官方包规范文档)。这一更新修复了 Vite、webpack 等主流打包器的一系列问题,并使得 MUI 各包可以在 Node.js 下通过 ES 模块直接加载。

从当前仓库的 packages/mui-material/package.json 可以直接看到这套 exports 布局的落地形态:

"exports": {
  ".": "./src/index.js",
  "./ButtonBase/TouchRipple": "./src/ButtonBase/TouchRipple.js",
  "./Grid": "./src/Grid/index.ts",
  ...
  "./*": "./src/*/index.js"
}

注意最后一条 "./*": "./src/*/index.js" 通配规则:它只允许一层深度的子路径导入。这正是下文「深层导入失效」这一破坏性变更的根源——例如 @mui/material/styles/createTheme(两层)不再可解析,而 @mui/material/Grid(一层)仍然合法。

1.2 其他质量改进

  • 全部组件的 slot 模式(slot pattern)用法统一化;
  • 支持 CSS layers:客户端应用通过 StyledEngineProviderenableCssLayer 属性开启,Next.js App Router 应用则通过 AppRouterCacheProvider 开启;
  • 移除了已废弃的 API,缩小 API 面,使文档更易于导航。

1.3 需要升级到 7.0.0 的包清单

如果你使用了以下任何包,都应把版本同步升到 "7.0.0"

  • @mui/icons-material
  • @mui/system
  • @mui/lab
  • @mui/material-nextjs
  • @mui/styled-engine
  • @mui/styled-engine-sc
  • @mui/utils

而 MUI X 系列包不遵循与 Material UI 相同的版本策略,升级过程中应保持版本不变:

  • @mui/x-data-grid@mui/x-data-grid-pro@mui/x-data-grid-premium
  • @mui/x-date-pickers@mui/x-date-pickers-pro
  • @mui/x-charts
  • @mui/x-tree-view@mui/x-tree-view-pro

二、最低 TypeScript 版本提升至 4.9

v7 将最低支持的 TypeScript 版本从 v4.7 提升到 4.9。官方说明其对齐依据是 DefinitelyTyped(npm 上 @types 命名空间背后的类型库)的支持窗口策略;并承诺不会在 Material UI 的 minor 版本中再次变更最低支持版本。

对于 @types/react* 系列包,需确保其主版本与你项目使用的 react 主版本一致。按需执行(把 <version> 替换为你使用的 react 主版本号):

# npm
npm install @types/react@<version> @types/react-dom@<version>

# pnpm
pnpm add @types/react@<version> @types/react-dom@<version>

# yarn
yarn add @types/react@<version> @types/react-dom@<version>

官方提醒:完成这一步后,务必确认应用仍能无错误运行,并先提交(commit)这些改动,再继续后续迁移步骤——这样每一步迁移的 diff 都可以独立回滚。

三、React 18 及更低版本:对齐 react-is 版本

3.1 操作步骤

如果你使用 React 18 或更低版本,需要把 react-is 强制解析为与你 react 版本一致的版本。以 react@18.3.1 为例:

第 1 步:安装对应版本的 react-is

# npm
npm install react-is@18.3.1

# pnpm
pnpm add react-is@18.3.1

# yarn
yarn add react-is@18.3.1

第 2 步:在 package.json 中配置 overrides / resolutions

// npm
{
  "overrides": {
    "react-is": "^18.3.1"
  }
}
// pnpm
{
  "overrides": {
    "react-is": "^18.3.1"
  }
}
// yarn
{
  "resolutions": {
    "react-is": "^18.3.1"
  }
}

3.2 为什么需要这样做

Material UI v7 依赖 react-is@19(仓库 packages/mui-material/package.json 中可见 react-is 依赖为 ^19.2.8),而 react-is@19 改变了 React 元素的识别方式。如果你停留在 React 18 或更低版本,react-is 版本不一致会在 prop type 检查中触发运行时错误;把 react-is 强制锁定为你的 React 版本即可避免这类问题。

四、破坏性变更逐项拆解

v7 是一个新的主版本,包含影响公共 API 的变更。以下按官方文档逐项给出修改方法。

4.1 包布局更新(Node.js exports 字段)

深层导入(超过一层)全面失效——它们本就被视为私有 API,现在会被打包器和运行时明确限制:

-import createTheme from '@mui/material/styles/createTheme';
+import { createTheme } from '@mui/material/styles';

modern 捆绑包被移除。此前 bundle 体积优化空间已不显著,如果你曾为这些 bundle 配置过 alias,必须现在删除:

 {
   resolve: {
     alias: {
-      '@mui/material': '@mui/material/modern',
-      '@mui/styled-engine': '@mui/styled-engine/modern',
-      '@mui/system': '@mui/system/modern',
-      '@mui/base': '@mui/base/modern',
-      '@mui/utils': '@mui/utils/modern',
-      '@mui/lab': '@mui/lab/modern',
     }
   }
 }

官方同时说明:早期版本指南曾提到存在 mui-modern 条件导出,现已移除;这属于非破坏性变化,打包器会自然回退到 ESM 捆绑包。

Vite 中针对 icons 包的强制 ESM alias 也应删除,因为已不再必要:

 // vite.config.js
   resolve: {
     alias: [
-      {
-        find: /^@mui\/icons-material\/(.*)/,
-        replacement: "@mui/icons-material/esm/$1",
-      },
     ],
   },

主题类型增强(module augmentation):如果你曾对嵌套导入路径声明模块增强,需要改为增强 @mui/material/styles;部分接口在新路径下导出名不同,可能需要同名重命名:

-declare module '@mui/material/styles/createTypography' {
+declare module '@mui/material/styles' {
-  interface TypographyOptions {
+  interface TypographyVariantsOptions {
     // ...
   }

-  interface Typography {
+  interface TypographyVariants {
     // ...
   }
 }

4.2 Grid 与 Grid2 改名

这是 v7 中影响面最大的组件级变更,有三条路径可选:

已废弃的 Grid 组件被改名为 GridLegacyGrid2 组件则迁入 Grid 命名空间。

路径 1:使用旧 Grid 且希望升级 —— 运行官方 codemod:

npx @mui/codemod@latest v7.0.0/grid-props <path/to/folder>

该 codemod 的仓库源码位于 grid-props/index.js。从 grid-props.js 的实现可以看出它的能力边界:它同时识别来自 @mui/material/Grid@mui/system/Grid@mui/joy/Grid 的默认导入以及 @mui/material / @mui/system / @mui/joy 桶导入中的具名 Grid;默认断点集合为 xs, sm, md, lg, xl,并支持通过 muiBreakpoints 选项传入自定义断点、通过 packageName 选项扩展识别的包名;此外它对 .json.d.ts 文件直接跳过。

路径 2:继续使用旧 Grid(改名跟随) —— 更新所有 Grid 引用:

 // imports
-import Grid, { gridClasses, GridProps } from '@mui/material/Grid';
+import Grid, { gridLegacyClasses, GridLegacyProps } from '@mui/material/GridLegacy';

-import { Grid } from '@mui/material';
+import { GridLegacy as Grid } from '@mui/material';

 // theme
 const theme = createTheme({
   components: {
-    MuiGrid: {
+    MuiGridLegacy: {
       // ...
     },
   },
 });

 // CSS classes
-.MuiGrid-root
+.MuiGridLegacy-root

路径 3:使用 Grid2(迁入新命名空间) —— 更新所有 Grid2 引用:

 // imports
-import Grid, { grid2Classes as gridClasses, Grid2Props as GridProps } from '@mui/material/Grid2';
+import Grid, { gridClasses, GridProps } from '@mui/material/Grid';

-import { Grid2 as Grid } from '@mui/material';
+import { Grid } from '@mui/material';

 // theme
 const theme = createTheme({
   components: {
-    MuiGrid2: {
+    MuiGrid: {
       // ...
     },
   },
 });

 // CSS classes
-.MuiGrid2-root
+.MuiGrid-root

4.3 InputLabel 的 size 属性标准化

InputLabelsize 属性从此遵循 ButtonTextField 等组件的通用命名约定:'normal''medium' 取代。若你显式使用过 size="normal",改为 size="medium"

-<InputLabel size="normal">Label</InputLabel>
+<InputLabel size="medium">Label</InputLabel>

默认行为不变(未显式设置 size 的代码无需改动)。官方提供 codemod 自动完成替换:

npx @mui/codemod@latest v7.0.0/input-label-size-normal-medium <path/to/folder>

其实现 input-label-size-normal-medium.js 基于 jscodeshift:查找所有 InputLabel JSX 元素,将其 size 属性中值为 'normal' 的静态字面量(含字符串表达式形式)直接改写为 j.literal('medium'),动态值则保持不动。

注意:由于 InputLabel 的默认 size 从 normal 变为 medium,类名 MuiInputLabel-sizeMedium 将不再被添加。如果你依赖该类名做自定义样式,请改用其他类。

4.4 SvgIcon 移除 data-testid

@mui/icons-material 中图标的默认 data-testid 属性在生产构建中被移除。该变更确保 data-testid 只在需要处定义,降低命名冲突的可能,并去除生产环境中的冗余属性。

4.5 TablePaginationActions 类型导入路径变更

类型导入路径由 @mui/material/TablePagination/TablePaginationActions 改为 @mui/material/TablePaginationActions

- import type { TablePaginationActionsProps } from '@mui/material/TablePagination/TablePaginationActions';
+ import type { TablePaginationActionsProps } from '@mui/material/TablePaginationActions';

4.6 主题行为变化:cssVariables + 双色方案下 theme 对象不再随 mode 切换

当启用 CSS 主题变量并同时内置 light/dark 双色方案时,切换深浅模式后 theme 对象本身不再变化useColorSchememode 状态会变,但 useTheme() 返回的对象不再变)。官方示例:

import {
  ThemeProvider,
  createTheme,
  useTheme,
  useColorScheme,
} from '@mui/material/styles';

const theme = createTheme({
  cssVariables: {
    colorSchemeSelector: 'class',
  },
  colorSchemes: {
    light: true,
    dark: true,
  },
});
console.log(theme.palette.mode); // 'light' is the default mode

function ColorModeToggle() {
  const { setMode, mode } = useColorScheme();
  const theme = useTheme();

  React.useEffect(() => {
    console.log(mode); // 首次渲染输出 'light',点击按钮后输出 'dark'
  }, [mode]);

  React.useEffect(() => {
    // 仅首次渲染输出 'light',点击按钮后不再触发
    console.log(theme.palette.mode);
  }, [theme]);

  return <button onClick={() => setMode('dark')}>Toggle dark mode</button>;
}

function App() {
  return (
    <ThemeProvider theme={theme}>
      <ColorModeToggle />
    </ThemeProvider>
  );
}

这一默认行为的目的是避免切换模式时产生不必要的重渲染,从而提升性能。相应的样式写法建议调整为:

推荐:用 theme.vars.* 直接引用 CSS 变量

const Custom = styled('div')(({ theme }) => ({
  color: theme.vars.palette.text.primary,
  background: theme.vars.palette.primary.main,
}));

需要运行时计算时:优先用 CSS 而非 JavaScript,例如借助 color-mix 函数调整颜色透明度(可参考 MDN 上 color-mix 的文档):

const Custom = styled('div')(({ theme }) => ({
  color: `color-mix(in srgb, ${theme.vars.palette.text.primary}, transparent 50%)`,
}));

CSS 方案不可行时:从 theme.colorSchemes 分别取 light/dark 的值并同时应用两种模式样式

const Custom = styled('div')(({ theme }) => ({
  color: alpha(theme.colorSchemes.light.palette.text.primary, 0.5),
  ...theme.applyStyles('dark', {
    color: alpha(theme.colorSchemes.dark.palette.text.primary, 0.5),
  }),
}));

兜底:opt-out —— 若以上方式均不适合项目,可给 ThemeProvider 传入 forceThemeRerender 属性恢复旧行为:

<ThemeProvider forceThemeRerender />

该属性在仓库源码 ThemeProvider.tsx 中有对应实现与测试(同目录 ThemeProviderWithVars.test.js),可确认其是 v7 起正式的公共 API。

4.7 v5 起废弃的 API 已移除

以下 API 在 v7 中被正式移除。逐项给出替代方案:

createMuiTheme 函数

已移除,改用 createTheme

-import { createMuiTheme } from '@mui/material/styles';
+import { createTheme } from '@mui/material/styles';

Dialog 的 onBackdropClick 属性

已移除,改用 onClose 回调(接收事件对象与关闭原因 reason):

function Example() {
  const [open, setOpen] = React.useState(false);

  const handleClose = (event, reason) => {
    if (reason === 'backdropClick') {
      // 处理背景点击
    }
    setOpen(false);
  };

  return (
    <Dialog open={open} onClose={handleClose}>
      {/* Dialog content */}
    </Dialog>
  );
}

experimentalStyled 函数

已移除,改用 styled

-import { experimentalStyled as styled } from '@mui/material/styles';
+import { styled } from '@mui/material/styles';

Hidden 与 PigmentHidden 组件

已移除。implementation="css"sx 属性替代:

-<Hidden implementation="css" xlUp><Paper /></Hidden>
+<Paper sx={{ display: { xl: 'none', xs: 'block' } }} />
-<Hidden implementation="css" mdDown><Paper /></Hidden>
+<Paper sx={{ display: { xs: 'none', md: 'block' } }} />

implementation="js"useMediaQuery hook 替代:

-<Hidden implementation="js" xlUp><Paper /></Hidden>
+const hidden = useMediaQuery(theme => theme.breakpoints.up('xl'));
+return hidden ? null : <Paper />;

Modal 的 onBackdropClick 属性

已移除,同样改用 onClose 回调(语义与 Dialog 一致,通过 reason === 'backdropClick' 区分背景点击):

function Example() {
  const [open, setOpen] = React.useState(false);

  const handleClose = (event, reason) => {
    if (reason === 'backdropClick') {
      // 处理背景点击
    }
    setOpen(false);
  };

  return (
    <Modal open={open} onClose={handleClose}>
      {/* Modal content */}
    </Modal>
  );
}

Rating 的 MuiRating-readOnly CSS 类

已移除,由全局的 Mui-readOnly 类取代:

-.MuiRating-readOnly
+.Mui-readOnly

StepButtonIcon 类型

已移除,改用 StepButtonProps['icon']

-import { StepButtonIcon } from '@mui/material/StepButton';
+import { StepButtonProps } from '@mui/material/StepButton';

-StepButtonIcon
+StepButtonProps['icon']

StyledEngineProvider 导入路径

'@mui/material' 导入 StyledEngineProvider 已废弃并移除,改从 '@mui/material/styles' 导入:

-import { StyledEngineProvider } from '@mui/material';
+import { StyledEngineProvider } from '@mui/material/styles';

Lab 组件迁入主包

以下 @mui/lab 组件与 hook 已迁入 @mui/material

  • Alert、AlertTitle、Autocomplete、AvatarGroup
  • Pagination、PaginationItem、Rating、Skeleton
  • SpeedDial、SpeedDialAction、SpeedDialIcon
  • ToggleButton、ToggleButtonGroup
  • usePagination

继续使用时请改从 @mui/material 导入:

-import Alert from '@mui/lab/Alert';
+import Alert from '@mui/material/Alert';

-import { Alert } from '@mui/lab';
+import { Alert } from '@mui/material';

官方 codemod 自动更新导入:

npx @mui/codemod@latest v7.0.0/lab-removed-components <path/to/folder>

官方警告:该 codemod 不覆盖组件相关的类型导入,类型导入需要手动检查修改。

五、从仓库源码看 v7 codemod 工具集

以上 codemod 命令都由仓库内的 packages/mui-codemod 包提供,v7 版本的全部转换器位于 packages/mui-codemod/src/v7.0.0 目录下,包括:

lab-removed-components.js 为例,其 REMOVED_EXPORTS 列表(Alert、AlertTitle、Autocomplete、AvatarGroup、Pagination、PaginationItem、Rating、Skeleton、SpeedDial、SpeedDialAction、SpeedDialIcon、ToggleButton、ToggleButtonGroup、usePagination 共 14 项)与官方文档给出的迁移组件清单完全一致;实现上它处理两类导入:@mui/lab/<Component> 的组件文件导入被整体改写为 @mui/material/<Component>,而 @mui/lab 桶导入中被移出的具名绑定会被「搬迁」到 @mui/material 桶导入(若不存在则新建),其余留在 @mui/lab 的具名绑定保持不动。其同目录下的 test-cases 覆盖了组件文件导入、桶导入、混合导入、部分迁移等实际场景,可作为迁移行为可信度的验证依据。每个转换器均配有 .test.jsactual/expected 用例,保证转换结果可回归验证。

六、废弃 API 清理:非强制、可分步进行

官方特别说明:升级 v7 不要求你立即处理所有废弃项。这些 v5 起废弃的 API 在 v7 中仍可用,你可以按自己的节奏对照官方的「deprecated APIs 迁移页」逐一清理——但它们将在下一个主版本中被真正移除。因此合理的策略是:先完成本文第四节的破坏性变更迁移,跑通构建与测试,再分批次执行废弃项清理。

七、迁移检查清单

  1. @mui/material@mui/icons-material@mui/system@mui/lab@mui/material-nextjs@mui/styled-engine@mui/styled-engine-sc@mui/utils 全部升至 7.0.0;MUI X 各包保持原版本。
  2. 确认 TypeScript ≥ 4.9;@types/react/@types/react-dom 主版本与 react 一致;提交本次改动。
  3. React ≤ 18 的项目:安装同版本 react-is,并在 package.json 配置 overrides(npm/pnpm)或 resolutions(yarn);提交本次改动。
  4. 清理 webpack/Vite 配置中的 modern alias 与 icons 包 ESM alias;把深层导入(如 styles/createTheme)改为一层导入;主题类型增强路径改为 @mui/material/styles 并核对接口改名(TypographyOptionsTypographyVariantsOptions 等)。
  5. 执行 npx @mui/codemod@latest v7.0.0/grid-props <path> 完成 Grid 迁移(或手工改为 GridLegacy/Grid 命名空间引用,含 MuiGrid* 主题键与 CSS 类名)。
  6. 执行 npx @mui/codemod@latest v7.0.0/input-label-size-normal-medium <path> 处理 size="normal";检查依赖 MuiInputLabel-sizeMedium 类的自定义样式。
  7. 执行 npx @mui/codemod@latest v7.0.0/lab-removed-components <path> 处理 Lab 组件导入;手动补查类型导入。
  8. 全局搜索并替换已移除 API:createMuiThemeonBackdropClick(Dialog/Modal)、experimentalStyledHidden/PigmentHiddenStepButtonIconMuiRating-readOnlyTablePagination/TablePaginationActions 路径。
  9. 使用 cssVariables + 双色方案的项目:审查主题切换逻辑,按 theme.vars / color-mix / theme.colorSchemes 三种方式改写样式;确需旧行为时给 ThemeProviderforceThemeRerender
  10. 跑完整构建、类型检查与测试;最后对照废弃 API 清单制定分阶段清理计划。
登录后查看全文
热门项目推荐
相关项目推荐