首页
/ Material UI 默认主题全解析:createTheme() 的组装机制与默认主题查看器

Material UI 默认主题全解析:createTheme() 的组装机制与默认主题查看器

2026-09-06 16:47:51作者:柏廷章Berta

本文围绕 Material UI(MUI)官方文档中的「Default theme viewer」页面展开,深入讲解默认主题对象是如何由 createTheme() 逐层组装出来的:包括 palettetypographyshadowstransitionszIndexmotion 等顶层模块的默认值来源,以及文档站内那棵可交互的主题树(Theme Viewer)的实现原理。读完本文,你将能在浏览器控制台里完整探索默认主题对象、理解 colorSchemes / cssVariables 的新特性,并能通过 ?expand-path= 查询参数精准定位任意主题节点。

文档页在讲什么:一棵可探索的默认主题树

官方文档的 default-theme 页面(default-theme.md)本身只有一段简短说明:

This tree view allows you to explore how the theme object looks like with the default values.

它提供了一个树状视图(tree view),让开发者直接"看见" createTheme() 不传任何参数时生成的完整主题对象长什么样。文档同时给出三个关键指引:

  1. 主题如何组装:去看 packages/mui-material/src/styles/createTheme.ts 及其相关的导入模块——createTheme() 内部依次调用 createPalettecreateTypographycreateTransitionscreateMixins 等工厂函数拼装出完整对象;
  2. 在浏览器里动手:文档站点在所有文档页面都暴露了 theme 变量(即 window.theme),可以直接在控制台把玩文档站使用的主题对象;
  3. 一个重要警告:文档站点使用的是 MUI 组织品牌定制主题(custom theme),因此页面上看到的 window.theme 并不是纯默认主题,而是一棵被 MUI 品牌色覆盖过的树。

页面的主体是一个内嵌 demo:DefaultTheme.js。下面逐层拆解这个 demo 与它背后引用的主题实现源码。

createTheme() 的组装入口:createTheme.ts

默认主题的入口实现在 createTheme.ts。它的签名是 createTheme(options, ...args),其中 ...args 会被深度合并进最终主题,这一行为在 createThemeNoVars.js 中由 args.reduce((acc, argument) => deepmerge(acc, argument), muiTheme) 一行完成。

入口函数根据 cssVariables 选项分成三条路径:

// createTheme.ts(节选)
const {
  palette,
  cssVariables = false,
  colorSchemes: initialColorSchemes = !palette ? { light: true } : undefined,
  defaultColorScheme: initialDefaultColorScheme = palette?.mode,
  ...other
} = options;
  1. cssVariables === false 且未显式传入 colorSchemes:直接走 createThemeNoVars(options),行为"与 v5 完全一致"(源码注释原话 // Behaves exactly as v5)。这就是默认路径,也就是默认主题查看器展示的那棵树;
  2. cssVariables === false 但显式传了 colorSchemes:在 NoVars 主题上手动挂 defaultColorScheme / colorSchemes,并为每个 scheme 用 attachColorScheme() 生成独立 palette,再逐 scheme 解析 focusVisible 外框颜色;
  3. cssVariables !== false:走 createThemeWithVars(),主题值会进一步被编译为 CSS 变量(theme.vars),供运行时切换深浅色模式使用。

可以推断:新版 MUI 把"单色板主题"重构成了"多 colorScheme 主题"——palette.mode 只是第一个(默认)scheme 的输入,colorSchemes 允许一个主题对象同时携带 light / dark 两套完整调色板。

默认主题的逐模块构成:createThemeNoVars.js

真正拼装那棵默认主题树的是 createThemeNoVars.js

let muiTheme = deepmerge(systemTheme, {
  mixins: createMixins(systemTheme.breakpoints, mixinsInput),
  palette,
  shadows: shadows.slice(),
  typography: createTypography(palette, typographyInput),
  motion: createMotion(motionInput),
  transitions: createTransitions(transitionsInput),
  zIndex: { ...zIndex },
});

注意第一层是 systemTheme——它来自 @mui/system/createTheme(options),提供了 Material 之外更底层的 breakpointsspacingshapecomponentsunstable_sxConfig 等字段。随后 deepmerge 叠加 Material 专属模块,最后再合并用户传入的 other 字段。也就是说,一棵完整默认主题的顶层字段 = System 基础层 + Material 定制层 + 用户覆盖层三层深度合并的产物。

palette:调色板

palettecreatePalette() 生成。不传任何选项时,MUI 使用 Material Design 的经典蓝色(primary.main#1976d2)以及一套 greysuccesserrorwarninginfo 等语义色阶,并自动推导 contrastText 等对比色字段。文档中 palette.md 引导读者"用主题查看器探索默认调色板",其链接正是指向本页并附带 ?expand-path=$.palette 参数。

shadows:25 级阴影

默认阴影表定义在 shadows.js:一个长度为 25 的数组,索引 0 是 'none',索引 1~24 由 createShadow() 生成,每级阴影都由三层叠加而成——

  • 关键影(umbra),透明度 0.2;
  • 关键半影(penumbra),透明度 0.14;
  • 环境影(ambient),透明度 0.12。

数值来源注释明确标注取自 material-components-web 的 elevation 变量(mdc-elevation/_variables.scss),因此 theme.shadows[3] 这类取值与 Material 设计规范中的 elevation 等级一一对应。

transitions:动画时长与缓动

createTransitions.js 提供四组默认缓动曲线和七档默认时长:

缓动 曲线 适用场景(源码注释)
easeInOut cubic-bezier(0.4, 0, 0.2, 1) 最常用的通用曲线
easeOut cubic-bezier(0.0, 0, 0.2, 1) 元素从屏外进入、减速停在原位
easeIn cubic-bezier(0.4, 0, 1, 1) 元素离开屏幕,不减速
sharp cubic-bezier(0.4, 0, 0.6, 1) 可能随时返回屏幕的对象
时长 毫秒 定位
shortest 150 最短
shorter 200
short 250
standard 300 最基础的推荐时长(create() 的默认值)
complex 375 复杂动画
enteringScreen 225 元素进入屏幕
leavingScreen 195 元素离开屏幕

主题上的 transitions.create(props, options) 会把这些拼成 prop duration easing delay 字符串;getAutoHeightDuration(height) 则按 4 + 15·h^0.25 + h/5 的公式为展开/收起动画动态计算时长(上限 3000ms)。

zIndex:全局层级表

zIndex.js 用注释强调"zIndex 像浏览器里的全局值,必须集中定义"。默认值:

mobileStepper 1000
fab / speedDial 1050
appBar 1100
drawer 1200
modal 1300
snackbar 1400
tooltip 1500

各组件内部都从这里取值,保证 Snackbar 永远浮在 Modal 之上、Tooltip 永远最顶层的相对顺序。

motion 与 reducedMotion

createMotion.js 只有几行:默认返回 { reducedMotion: 'never' } 再合并用户输入。注意一个容易混淆的细节——createThemeNoVars.js 中有一行 delete muiTheme.transitions.reducedMotion第 120 行),因为新版主题把 reducedMotion 的归属从 transitions 迁到了 motion,需要清掉 system 层遗留的旧值。

typography 与 mixins

typographycreateTypography(palette, typographyInput) 基于调色板生成(字体族、字号、行高等),typography.md 同样提供"主题查看器 + window.theme.typography 控制台"两种探索方式;mixinscreateMixins(breakpoints) 生成,默认包含 toolbar 等高/边距工具类,且依赖 @mui/system 层提供的 breakpoints(各断点文档 breakpoints.md 也是通过本页的 ?expand-path=$.breakpoints 链接来展示默认断点值的)。

颜色操作工具与 sx

拼装完成后,createThemeNoVars.js 还挂了几个运行时工具:

  • theme.alpha(color, coefficient) / theme.lighten() / theme.darken():当主题开启了 colorSpace 时,会输出 oklch()color-mix() 表达式;开启 CSS 变量时则把 var(--xxx) 替换为 var(--xxxChannel) 以正确计算 alpha(见 attachColorManipulators);
  • theme.unstable_sx(props):主题对象上的 sx() 快捷函数,内部调用 @mui/systemstyleFunctionSx
  • theme.toRuntimeSource:即 stringifyTheme,源码注释标明是"for Pigment CSS integration",即主题对象可被序列化回运行时 CSS 源码;
  • 开发环境下(NODE_ENV !== 'production')还有一段守护逻辑:如果用户在 components.*.styleOverrides 里直接写 focuseddisabled 等内部状态类名,会 console.error 提示改用 &.Mui-focused 语法,并把该样式清空以防全局污染。

内置默认主题常量:defaultTheme.js

仓库里还有一个最小入口 defaultTheme.js

import createTheme from './createTheme';

const defaultTheme = createTheme();

export default defaultTheme;

它调用的正是本文拆解的零参 createTheme(),导出的就是"纯净默认主题"对象,供包内组件(如未包裹 ThemeProvider 时)兜底使用。

默认主题查看器的实现:DefaultTheme.js

文档页的树状 demo(DefaultTheme.js)本身就值得细读,它展示了"如何在页面里交互式探索一棵主题对象"的完整套路。

1. 实时生成、剔除内部字段

const data = React.useMemo(() => {
  const themeData = createTheme({
    palette: { mode: darkTheme ? 'dark' : 'light' },
  });
  const { unstable_sxConfig: unstableSxConfig, unstable_sx: unstableSx, ...rest } = themeData;
  return rest;
}, [darkTheme]);

组件用 useMemo 调用 createTheme() 现场生成主题树,并把 unstable_sxConfigunstable_sx 两个非序列化友好的内部字段解构剔除,只把纯数据 rest 交给树形组件渲染。深色开关切换 palette.mode 后,palettetypography 中依赖调色板的字段会随之重新计算——这正是"看默认值"最直观的方式。

2. 通过 URL 参数 expand-path 深度展开

其他文档页(palette / typography / breakpoints)都是链接到本页并带 ?expand-path=$.palette 之类的查询参数。demo 在 useEffect 里解析它:

decodeURI(document.location.search.slice(1))
  .split('&')
  .forEach((param) => {
    const [name, value] = param.split('=');
    if (name === 'expand-path') {
      expandPath = value;
    }
  });

拿到 $.palette 后,去掉 $. 前缀、按 . 拆段并累加还原成路径数组 ['palette'](嵌套写法如 $.typography.fontFamily 会还原为 ['typography', 'typography.fontFamily']),传给 ThemeViewerexpandPaths。于是从 palette 文档页点过来时,树自动展开到调色板分支——这就是文档体系里"主题查看器"作为共享锚点的机制。

3. 全展开开关与两个预构建视图

顶栏两个 StyledSwitch 分别控制:

  • Expand all:不直接重渲染整棵树,而是在 第 125~133 行useMemo 预构建了两份 ThemeViewer(一份 expandPaths={[]} 折叠态、一份 expandPaths={allNodeIds} 全展开态),切换开关只改 CSS display,避免展开几百个节点时的卡顿;
  • Dark mode:即前文 createTheme({ palette: { mode } }) 的驱动源。

allNodeIds 来自 useItemIdsLazy(data)(ThemeViewer 的懒遍历工具),会递归收集树的每个节点 id。

4. 与文档站主题的区分

页面顶部还有一个文档站全局样式的小细节:demo 中 Switch 的暗色适配选择器写成 [`*:where(${theme.vars ? '[data-mui-color-scheme="dark"]' : '.mode-dark'}) &`],兼容"开启 CSS 变量时按 [data-mui-color-scheme] 属性、否则按 .mode-dark 类名"两种主题方案。也正因为文档站启用了自定义品牌主题,文档才特别提醒:页面上 window.theme 不是纯默认值——要看"出厂设置",请依赖本文展示的树视图(它现场 createTheme() 生成,不受站点主题影响)或本地 node/控制台执行 createTheme()

在控制台探索默认主题:三种方式

结合文档与源码,实操时可按需选择:

  1. 文档页树视图:访问 /material-ui/customization/default-theme/,用开关切换深色模式、展开全树;
  2. 控制台变量:文档任意页面执行 theme.palettetheme.transitions.create('width') 等——但记住这是 MUI 品牌定制主题,palette.primary 等已被覆盖;
  3. 纯净默认值:本地执行 createTheme()(来自 @mui/material/styles),或直接阅读上文列出的各 create* 工厂函数源码——默认值全部硬编码在这些文件里,无运行时魔法。

小结

  • 默认主题由 createTheme.ts 分派,默认路径 createThemeNoVars.js 以"System 基础层 + Material 模块层"深度合并的方式组装;
  • 顶层默认值均有明确的源码出处:25 级 shadowsshadows.js)、7 档 zIndexzIndex.js)、4 组 easing + 7 档 durationcreateTransitions.js)、motion.reducedMotion: 'never'createMotion.js);
  • 新版主题把单 palette 模型升级为 colorSchemes + defaultColorScheme 多方案模型,并为 CSS 变量场景(cssVariables: true)预留了 createThemeWithVars.js 分支;
  • 文档站的 Default Theme Viewer 通过现场调用 createTheme()、剔除 unstable_* 字段、支持 ?expand-path=$.xxx 深链和双视图预渲染,成为整套 customization 文档共享的"默认值锚点";
  • 相关行为均有测试覆盖,例如 createTheme.test.jscreateTheme.spec.ts,可作为默认值断言的权威参照。
登录后查看全文
热门项目推荐
相关项目推荐