首页
/ Material UI v4 到 v5 迁移:样式与主题破坏性变更实战指南

Material UI v4 到 v5 迁移:样式与主题破坏性变更实战指南

2026-09-06 16:29:32作者:冯梦姬Eddie

Material UI v5 默认样式库从 JSS 切换为 Emotion,并重构了主题对象的结构,这使 v4 → v5 的迁移中“样式与主题”成为破坏性变更最集中的部分。本文基于官方迁移文档(v5-style-changes.md),逐项讲解 styleOverrides 选择器、主题结构、@mui/material/styles 导出、System 属性等全部变更,并结合当前仓库的源码实现(adaptV4Theme.jscreatePalette.jscolorManipulator.js)说明每项变更的底层行为。读完本文,你可以对照检查清单逐项完成样式与主题层面的迁移。

迁移总览:五步流程与 codemods 的作用

官方将 v5 迁移拆为五个部分,本篇覆盖第二部分(样式与主题),完整流程为:

  1. Getting started(主迁移指南,见 迁移文档目录);
  2. Breaking changes part one: style and theme(本文主题)
  3. Breaking changes part two: components(组件级变更);
  4. Migrating from JSS;
  5. Troubleshooting。

v5 引入了大量破坏性变更,其中很多可以借助官方 codemods 自动解决——codemods 的实现就在本仓库的 mui-codemod 包 中。判断标准很简单:在目录中用 ✅ 标记的变更项由 codemods 自动处理,如果你已按主迁移指南跑过 codemods,这些条目无需再手动操作;其余条目必须手动处理。

将主题的 styleOverrides 迁移到 Emotion

重构本地规则引用($ 语法)

主题中定义的样式覆盖在 v5 中可能“看起来还能工作”,但嵌套元素的样式机制已经改变:JSS 时代的 $ 本地规则引用语法在 Emotion 下不再有效,必须替换为合法的全局类选择器。

替换状态类名(state class)

 const theme = createTheme({
   components: {
     MuiOutlinedInput: {
       styleOverrides: {
         root: {
-          '&$focused': {
+          '&.Mui-focused': {
             borderWidth: 1,
           }
         }
       }
     }
   }
 });

将嵌套类选择器替换为全局类名

 const theme = createTheme({
   components: {
     MuiOutlinedInput: {
       styleOverrides: {
         root: {
-          '& $notchedOutline': {
+          '& .MuiOutlinedInput-notchedOutline': {
             borderWidth: 1,
           }
         }
       }
     }
   }
 });

更稳妥的做法是利用官方导出的 [component]Classes 常量,避免硬编码类名字符串:

+import { outlinedInputClasses } from '@mui/material/OutlinedInput';

 const theme = createTheme({
   components: {
     MuiOutlinedInput: {
       styleOverrides: {
         root: {
-          '& $notchedOutline': {
+          [`& .${outlinedInputClasses.notchedOutline}`]: {
             borderWidth: 1,
           }
         }
       }
     }
   }
 });

所有组件都导出了包含其全部嵌套类的 [component]Classes 常量,可以放心依赖它而不是手写类名。完整的全局状态类名列表见 Customization 文档的 “State classes” 章节。

重构空格/逗号分隔值的替代数组语法

JSS 支持用嵌套数组表达空格与逗号分隔的值(如多背景、多段 padding),Emotion 不支持这种语法,需要改写成字符串。

背景多值示例

Before:

const theme = createTheme({
  overrides: {
    MuiBox: {
      root: {
        background: [
          ['url(image1.png)', 'no-repeat', 'top'],
          ['url(image2.png)', 'no-repeat', 'center'],
          '!important',
        ],
      },
    },
  },
});

After:

const theme = createTheme({
  components: {
    MuiBox: {
      styleOverrides: {
        root: {
          background:
            'url(image1.png) no-repeat top, url(image2.png) no-repeat center !important',
        },
      },
    },
  },
});

注意为数值补上单位

// Before
padding: [[5, 8, 6]],

// After
padding: '5px 8px 6px',

这一点与下文 theme.spacing 返回值带 px 后缀的变更一致:Emotion 不会自动为数字补单位,凡是需要像素值的地方务必显式写明。

ref 相关的破坏性变更

移除对非 ref-forwarding 类组件的支持

component prop 或作为直接 children 传入的非 ref 转发类组件,其支持已被移除。

  • 如果你之前使用了 unstable_createStrictModeTheme,或在 React.StrictMode 下从未见过与 findDOMNode 相关的警告,则无需处理;
  • 否则请阅读 Composition 指南中 “Caveat with refs” 章节了解迁移方式。

此变更几乎影响所有使用 component prop 的组件,以及要求 children 必须是元素的场景(例如 <MenuList><CustomMenuItem /></MenuList>)。

收紧 ref 的类型约束

部分组件传入 ref 时会出现类型错误,需要使用更具体的元素类型。例如 Card 期望 HTMLDivElementListItem 期望 HTMLLIElement

 import * as React from 'react';
 import Card from '@mui/material/Card';
 import ListItem from '@mui/material/ListItem';

 export default function SpecificRefType() {
-  const cardRef = React.useRef<HTMLElement>(null);
+  const cardRef = React.useRef<HTMLDivElement>(null);

-  const listItemRef = React.useRef<HTMLElement>(null);
+  const listItemRef = React.useRef<HTMLLIElement>(null);
   return (
     <div>
       <Card ref={cardRef}></Card>
       <ListItem ref={listItemRef}></ListItem>
     </div>
   );
 }

各组件期望的具体元素类型:

@mui/material

组件 ref 类型
Accordion HTMLDivElement
Alert HTMLDivElement
Avatar HTMLDivElement
ButtonGroup HTMLDivElement
Card HTMLDivElement
Dialog HTMLDivElement
ImageList HTMLUListElement
List HTMLUListElement
Tab HTMLDivElement
Tabs HTMLDivElement
ToggleButton HTMLButtonElement

@mui/lab

组件 ref 类型
Timeline HTMLUListElement

样式库:调整 CSS 注入顺序

v5 默认样式库为 Emotion。如果你仍在用 JSS(例如 makeStyles)为 Material UI 组件做覆盖,就必须处理两套 <style> 的注入顺序:JSS 的 <style> 元素必须在 Emotion 的 <style> 元素之后注入到 <head>,否则你的覆盖会被 Material UI 自身样式压过。

✅ 使用 StyledEngineProvider 调整注入顺序

将带 injectFirst 选项的 StyledEngineProvider 放在组件树顶层:

import * as React from 'react';
import { StyledEngineProvider } from '@mui/material/styles';

export default function GlobalCssPriority() {
  return (
    {/* Inject Emotion before JSS */}
    <StyledEngineProvider injectFirst>
      {/* Your component tree. Now you can override Material UI's styles. */}
    </StyledEngineProvider>
  );
}

✅ 为自定义 Emotion cache 添加 prepend

如果你已有自定义 cache 并用 Emotion 给应用写样式,它会覆盖 Material UI 提供的 cache。修正注入顺序的方式是给 createCacheprepend 选项:

 import * as React from 'react';
 import { CacheProvider } from '@emotion/react';
 import createCache from '@emotion/cache';

 const cache = createCache({
   key: 'css',
+  prepend: true,
 });

 export default function PlainCssPriority() {
   return (
     <CacheProvider value={cache}>
       {/* Your component tree. Now you can override Material UI's styles. */}
     </CacheProvider>
   );
 }

:::warning 如果使用的是 styled-components,且 StyleSheetManager 带有自定义 target,请确保该 target 是 HTML <head> 中的第一个元素。可参考 @mui/styled-engine-sc 包中 StyledEngineProvider 的实现(位于 packages/mui-styled-engine-sc/src 目录)。 :::

主题结构(Theme structure)变更

v5 中主题对象的形状发生了重构,所有组件相关配置统一收拢到 components 键下。为了平滑过渡,官方提供了 adaptV4Theme 辅助函数,可渐进式地将旧主题升级为新结构。

✅ 使用 adaptV4Theme 辅助函数

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

-const theme = createMuiTheme({
+const theme = createTheme(adaptV4Theme({
   // v4 theme
-});
+}));

源码印证adaptV4Theme.js 展示了适配器实际做的事——将 v4 的 defaultProps/props 映射到 components[组件名].defaultProps(L28-L39),将 styleOverrides/overrides 映射到 components[组件名].styleOverrides(L41-L52),并重新生成 theme.spacing(L54-L55)。需要注意的是,源码开头(L4-L12)在开发环境下会打印 adaptV4Theme() is deprecated 的警告,即它只是过渡工具,最终仍需手动迁移到 v5 原生结构。

:::warning 该适配器只处理 createTheme() 的入参。如果你在创建主题后修改了主题形状,结构必须手动迁移。 :::

以下是适配器支持的各项具体变更:

移除 gutters 抽象

“gutters” 抽象被证明使用频率不够高而移除。注意:从源码结构看,适配器仍会在开发过渡期为旧主题补回 mixins.gutters 的等价实现(adaptV4Theme.js L57-L75:paddingLeft/paddingRight: spacing(2),在 sm 断点以上升级为 spacing(3)),但新代码应直接写:

-theme.mixins.gutters(),
+paddingLeft: theme.spacing(2),
+paddingRight: theme.spacing(2),
+[theme.breakpoints.up('sm')]: {
+  paddingLeft: theme.spacing(3),
+  paddingRight: theme.spacing(3),
+},

✅ theme.spacing 返回值带 px 后缀

theme.spacing 现在默认返回带 px 单位的字符串。这一变更改善了与 styled-components 和 Emotion 的集成(Emotion 不会给数字自动补单位):

// Before
theme.spacing(2) => 16

// After
theme.spacing(2) => '16px'

✅ theme.palette.type 重命名为 mode

theme.palette.type 键重命名为 theme.palette.mode,以贴合描述该功能的 “dark mode” 惯用术语:

 import { createTheme } from '@mui/material/styles';
-const theme = createTheme({ palette: { type: 'dark' } }),
+const theme = createTheme({ palette: { mode: 'dark' } }),

从源码看,适配器同时写入 modetype 两个键(adaptV4Theme.js L86-L87),保证过渡期间新旧代码都能读到正确的模式值。

默认 theme.palette.info 颜色变更

默认 info 色被调整为在亮色与暗色模式下都通过 WCAG AA 无障碍对比度标准:

  info = {
-  main: cyan[500],
+  main: lightBlue[700], // lightBlue[400] in "dark" mode

-  light: cyan[300],
+  light: lightBlue[500], // lightBlue[300] in "dark" mode

-  dark: cyan[700],
+  dark: lightBlue[900], // lightBlue[700] in "dark" mode
  }

默认 theme.palette.success 颜色变更

  success = {
-  main: green[500],
+  main: green[800], // green[400] in "dark" mode

-  light: green[300],
+  light: green[500], // green[300] in "dark" mode

-  dark: green[700],
+  dark: green[900], // green[700] in "dark" mode
  }

默认 theme.palette.warning 颜色变更

  warning = {
-  main: orange[500],
+  main: '#ED6C02', // orange[400] in "dark" mode

-  light: orange[300],
+  light: orange[500], // orange[300] in "dark" mode

-  dark: orange[700],
+  dark: orange[900], // orange[700] in "dark" mode
  }

源码印证createPalette.js 中的 getDefaultInfo(L161-L174)、getDefaultSuccess(L176-L189)、getDefaultWarning(L191-L204)与上述取值完全一致;其中 warning 的 main'#ed6c02',源码注释说明这是“最接近 orange[800] 且能通过 3:1 对比度”的值(L200)。

按需恢复 theme.palette.text.hint 键

theme.palette.text.hint 键在 Material UI 组件中未被使用,已被移除。如果业务代码依赖它,可以手动加回:

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

-const theme = createTheme(),
+const theme = createTheme({
+  palette: { text: { hint: 'rgba(0, 0, 0, 0.38)' } },
+});

注意适配器恢复该键时会按模式区分默认值:暗色模式为 rgba(255, 255, 255, 0.5),亮色模式为 rgba(0, 0, 0, 0.38)adaptV4Theme.js L81-L85)。

重构组件定义

主题中的组件定义被重组到 components 键下,便于查找。

1. props → components[组件].defaultProps

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

 const theme = createTheme({
-  props: {
-    MuiButton: {
-      disableRipple: true,
-    },
-  },
+  components: {
+    MuiButton: {
+      defaultProps: {
+        disableRipple: true,
+      },
+    },
+  },
 });

2. overrides → components[组件].styleOverrides

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

 const theme = createTheme({
-  overrides: {
-    MuiButton: {
-      root: { padding: 0 },
-    },
-  },
+  components: {
+    MuiButton: {
+      styleOverrides: {
+        root: { padding: 0 },
+      },
+    },
+  },
 });

@mui/styles(JSS 迁移包)相关变更

v5 不再内置 JSS,原 @mui/material/styles 中的 JSS 工具被拆分到独立的 @mui/styles 包(已标记废弃)。

更新 ThemeProvider 导入

如果同时使用 @mui/styles 工具与 @mui/material,应改用 @mui/material/styles 导出的 ThemeProvider,这样上下文中的 theme 同时对 makeStyleswithStyles@mui/styles 工具与 Material UI 组件可见:

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

由于 @mui/styles 工具不再提供 defaultTheme,务必在应用根部添加一个 ThemeProvider

✅ 为 DefaultTheme 添加模块增强(TypeScript)

@mui/styles 包不再属于 @mui/material/styles。如果两者并用,需要为 DefaultTheme 添加模块增强(module augmentation):

// in the file where you are creating the theme (invoking the function `createTheme()`)
import { Theme } from '@mui/material/styles';

declare module '@mui/styles' {
  interface DefaultTheme extends Theme {}
}

@mui/material/colors:颜色导入路径变更

✅ 变更颜色导入方式

超过一层的嵌套导入是私有的,不能再从 @mui/material/colors/red 导入 red

-import red from '@mui/material/colors/red';
+import { red } from '@mui/material/colors';

@mui/material/styles 导出项变更

这是变更密度最高的部分,几乎所有 JSS 时代从 @mui/material/styles 导出的工具都移动到了 @mui/styles

✅ fade 重命名为 alpha

fade() 重命名为 alpha(),以更好描述其功能。旧名在输入颜色本身已带 alpha 值时容易引起误解——该工具会覆盖颜色的 alpha 通道。

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

  const classes = makeStyles(theme => ({
-  backgroundColor: fade(theme.palette.primary.main, theme.palette.action.selectedOpacity),
+  backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity),
  }));

源码印证colorManipulator.jsalpha()(L245-L259)先对颜色值做 decomposeColor 解析,再把 alpha 通道直接赋值为传入参数(value 被钳制在 0-1 区间),文档注释明确写着 “Any existing alpha values are overwritten”(L240),与上述行为一致。

✅ 更新 createStyles 导入

createStyles@mui/material/styles 移到 @mui/styles 导出,目的是从 Material UI npm 包中移除对 @mui/styles 的依赖:

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

✅ 更新 createGenerateClassName 导入

createGenerateClassName 不再从 @mui/material/styles 导出。若需继续使用该函数,可从已废弃的 @mui/styles 包导入:

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

不使用 @mui/styles 而生成自定义类名,可参考文档中的 ClassName Generator(experimental-api)章节。

✅ createMuiTheme 重命名

createMuiTheme 重命名为 createTheme(),使其与 ThemeProvider 搭配使用时更直观:

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

-const theme = createMuiTheme({
+const theme = createTheme({

✅ 更新 MuiThemeProvider 导入

MuiThemeProvider 组件不再从 @mui/material/styles 导出,改用 ThemeProvider

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

✅ 更新 jssPreset 导入

jssPreset 对象不再从 @mui/material/styles 导出,可从已废弃的 @mui/styles 包继续导入:

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

✅ 更新 makeStyles 导入

Material UI v5 不再使用 JSS,基于 JSS 的 makeStyles 不再由 @mui/material/styles 导出。在迁移期间,可临时从 @mui/styles/makeStyles 导入这个已废弃的工具,之后再逐步重构组件。由于 defaultTheme 不再可用,务必在应用根部添加 ThemeProvider;与 @mui/material 并用时,推荐使用 @mui/material/stylesThemeProvider

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

+const theme = createTheme();
  const useStyles = makeStyles((theme) => ({
    background: theme.palette.primary.main,
  }));
  function Component() {
    const classes = useStyles();
    return <div className={classes.root} />
  }

  // In the root of your app
  function App(props) {
-  return <Component />;
+  return <ThemeProvider theme={theme}><Component {...props} /></ThemeProvider>;
  }

✅ 更新 ServerStyleSheets 导入

ServerStyleSheets 不再从 @mui/material/styles 导出,可从已废弃的 @mui/styles 包导入:

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

styled

v5 中,原 JSS 版 styled 被一个不向后兼容的 Emotion 等价实现取代。迁移期间可临时从 @mui/styles 导入 JSS 版,之后再重构。同样注意 defaultTheme 不可用,需手动提供 ThemeProvider

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

+const theme = createTheme();
  const MyComponent = styled('div')(({ theme }) => ({ background: theme.palette.primary.main }));

  function App(props) {
-  return <MyComponent />;
+  return <ThemeProvider theme={theme}><MyComponent {...props} /></ThemeProvider>;
  }

✅ 更新 StylesProvider 导入

StylesProvider 不再从 @mui/material/styles 导出,可从已废弃的 @mui/styles 包导入:

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

✅ 更新 useThemeVariants 导入

useThemeVariants 钩子不再从 @mui/material/styles 导出,可从已废弃的 @mui/styles 包导入:

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

✅ 更新 withStyles 导入

makeStyles 同理:JSS 版 withStyles 不再由 @mui/material/styles 导出,迁移期间可临时从 @mui/styles/withStyles 导入,并手动在根部提供 ThemeProvider

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

+const defaultTheme = createTheme();
  const MyComponent = withStyles((props) => {
    const { classes, className, ...other } = props;
    return <div className={clsx(className, classes.root)} {...other} />
  })(({ theme }) => ({ root: { background: theme.palette.primary.main }}));

  function App() {
-  return <MyComponent />;
+  return <ThemeProvider theme={defaultTheme}><MyComponent /></ThemeProvider>;
  }

✅ 用 ref 替换 innerRef

innerRef prop 替换为 ref prop,ref 现在会自动转发到内部组件:

  import * as React from 'react';
  import { withStyles } from '@mui/styles';

  const MyComponent = withStyles({
    root: {
      backgroundColor: 'red',
    },
  })(({ classes }) => <div className={classes.root} />);

  function MyOtherComponent(props) {
    const ref = React.useRef();
-  return <MyComponent innerRef={ref} />;
+  return <MyComponent ref={ref} />
  }

更新 withTheme 导入

withTheme HOC 已从 @mui/material/styles 移除,可改用 @mui/styles/withTheme。同样需要手动提供 ThemeProvider

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

+const theme = createTheme();
  const MyComponent = withTheme(({ theme }) => <div>{theme.direction}</div>);

  function App(props) {
-  return <MyComponent />;
+  return <ThemeProvider theme={theme}><MyComponent {...props} /></ThemeProvider>;
  }

✅ 移除 withWidth

该 HOC 已被移除。如需同等能力,可用 useMediaQuery 钩子实现替代方案(见文档 react-use-media-query 的 “migrating-from-withwidth” 章节)。

@mui/icons-material:GitHub 图标尺寸调整

GitHub 图标宽度从 24px 缩小到 22px,以与其他图标尺寸保持一致。这是一处无感知的视觉修正,无需代码改动。

@material-ui/pickers

@material-ui/pickers 迁移到 v5 有专门文档(pickers-migration 页面),不在本文范围内,请查阅官方文档。

System 变更

✅ 重命名 gap 相关 props

以下 System 函数与属性因属于被废弃的 CSS 写法而重命名:

  • gridGapgap
  • gridRowGaprowGap
  • gridColumnGapcolumnGap

✅ gap 属性需要带间距单位

gaprowGapcolumnGap 中使用间距单位。如果你之前传的是数字,现在需要显式写 px,以绕开基于 theme.spacing 的新换算逻辑:

  <Box
-  gap={2}
+  gap="2px"
  >

源码印证gap 的样式函数注册在 System 的默认 sx 配置中,见 defaultSxConfig.tsgap: { style: gap });grid 布局场景下的 gap 处理还可参考 gridGenerator.tscssGrid.ts

用 sx 替换 css prop

为避免与 styled-components 和 Emotion 的 css prop 冲突,css prop 改为 sx

-<Box css={{ color: 'primary.main' }} />
+<Box sx={{ color: 'primary.main' }} />

:::warning v4 中 System 的 grid 函数并未被文档化,因此不存在对应迁移项。 :::

迁移自查清单

完成本文各节后,建议按以下顺序自查:

  1. 主题对象:是否已改用 components 键(defaultProps + styleOverrides)?palette.mode 是否替换了 type
  2. styleOverrides 中是否还残留 $ 本地规则引用或嵌套数组值?
  3. 是否已运行 codemods?✅ 标记项若无残留报错可跳过;
  4. 与 JSS/Emotion 混用场景:StyledEngineProvider injectFirstcreateCache({ prepend: true }) 是否就位?
  5. @mui/material/styles 导入的 makeStyleswithStylesstyledwithTheme 等是否已改为从 @mui/styles 导入,并在根部提供 ThemeProvider
  6. TypeScript 项目:DefaultTheme 模块增强是否已添加?ref 类型是否收紧到具体元素类型?
  7. System 用法:gridGap 系列是否改为 gap/rowGap/columnGap 并显式带上 px 单位?css prop 是否替换为 sx

完成以上内容后,即可进入 v5 迁移的第三部分——组件级破坏性变更(v5-component-changes),继续完成整个迁移流程。

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