Material UI theme.spacing() 间距系统详解:从 8px 缩放因子到源码级实现原理
本文基于 Material UI 官方自定义文档 Spacing 展开,讲解 theme.spacing() 间距工具函数的默认机制、数字 / 函数 / 数组三种自定义配置方式、多参数(Multiple arity)签名,以及字符串混用规则;并结合仓库中 packages/mui-system 与 packages/mui-material 的真实源码与测试用例,剖析每种配置形态在底层如何被解析和转换,帮助你在项目主题定制中正确、安全地控制整个 UI 的间距体系。
默认机制:8px 缩放因子
Material UI 默认采用 Material Design 规范推荐的 8px 缩放因子来生成元素间距。直接使用 createTheme() 创建主题时:
const theme = createTheme();
theme.spacing(2); // `${8 * 2}px` = '16px'
从源码结构看,这一默认值由两处共同保证:
- createSpacing 的默认参数即
spacingInput: SpacingOptions = 8,注释中明确引用了 Material Design 布局规范中“多数测量值对齐 8dp 网格”的设计原则; - createUnarySpacing 在调用
createUnaryUnit(theme, 'spacing', 8, 'spacing')时将8作为兜底默认值(defaultValue)。
官方测试 createTheme.test.js 也直接验证了该行为:expect(theme.spacing(1)).to.equal('8px')。
theme.spacing() 的完整 TypeScript 签名在 createSpacing.ts 中定义,支持 0~4 个参数:
export interface Spacing {
(): string;
(value: SpacingArgument): string;
(topBottom: SpacingArgument, rightLeft: SpacingArgument): string;
(top: SpacingArgument, rightLeft: SpacingArgument, bottom: SpacingArgument): string;
(
top: SpacingArgument,
right: SpacingArgument,
bottom: SpacingArgument,
left: SpacingArgument,
): string;
}
自定义间距:数字、函数、数组三种形态
官方文档给出了三种修改间距转换规则的方式,以下逐一说明并结合源码解释其底层处理逻辑。
方式一:提供一个数字
数字代表每个 spacing 单位的像素值:
const theme = createTheme({
spacing: 4,
});
theme.spacing(2); // `${4 * 2}px` = '8px'
在 createThemeWithVars.js 中,getSpacingVal 会把数字输入规范化为 ${spacingInput}px 字符串(spacing: 4 → '4px'),随后交给 createSpacing(input.spacing, createUnarySpacing(this)) 生成最终的 theme.spacing 函数。
方式二:提供一个函数
函数签名 (factor) => ... 让你完全接管“因子 → CSS 值”的映射,文档以 Bootstrap 的 rem 策略为例:
const theme = createTheme({
spacing: (factor) => `${0.25 * factor}rem`, // (Bootstrap strategy)
});
theme.spacing(2); // = 0.25 * 2rem = 0.5rem = 8px
从源码结构看,createUnaryUnit 中,当 themeSpacing 是函数时会直接原样返回该函数:if (typeof themeSpacing === 'function') { return themeSpacing; },即完全信任用户的实现。这一点也由 createSpacing.test.ts 覆盖:createSpacing((factor) => ${0.25 * factor}rem) 时 spacing(2) 得到 '0.5rem'。
需要注意的是:函数形态下如果因子是字符串(见下文 theme.spacing(1, 'auto')),默认的 createUnarySpacing 会原样透传字符串,但若你自己提供的函数没有处理字符串入参,需要自行兼容——这也是官方推荐函数形态的另一个原因(见下节)。
方式三:提供一个数组
数组中的值按索引取值,返回数组项对应的 CSS 值(数值会被加上 px):
const theme = createTheme({
spacing: [0, 4, 8, 16, 32, 64],
});
theme.spacing(2); // = '8px'
数组形态的底层实现在 createUnaryUnit,有几个从源码可以直接确认的行为细节:
- 取绝对值作为索引:
const abs = Math.abs(val),theme.spacing(2)取themeSpacing[2]; - 支持负数取反:负值会返回相反符号的结果——数值结果直接取负,CSS 变量字符串(
var(...)开头)会被包装成calc(-1 * var(...)),普通字符串则加-前缀; - 开发态校验:若传入非整数(如
0.5),会打印MUI: The "theme.spacing" array type cannot be combined with non integer values.警告;若索引越界(如数组只有 6 项却传6),会打印MUI: The value provided (6) overflows.并列出受支持的范围; - 字符串透传:
typeof val === 'string'时直接原样返回,因此数组形态同样能配合theme.spacing(1, 'auto')使用。
官方测试 createSpacing.test.ts 也验证了数组可接受数字或字符串元素:createSpacing(['0rem', '8rem', '16rem']) 时 spacing(2) 得到 '16rem'。
数组形态的局限与推荐的函数替代方案
官方文档特别警告:数组形态只能配合正整数索引使用,不支持 theme.spacing() 的全部签名,例如 theme.spacing(0.5)(小数会被截断取整索引)、theme.spacing(-1)(负数在部分场景不符合预期)、或需要超出数组长度范围的值。文档给出的建议是:如果必须使用数组,改用能处理所有可能签名的函数形态,完整示例如下(继承自原文档):
const spacings = [0, 4, 8, 16, 32, 64];
const theme = createTheme({
spacing: (factor: number | 'auto' = 1) => {
if (factor === 'auto') {
return 'auto';
}
const sign = factor >= 0 ? 1 : -1;
const factorAbs = Math.min(Math.abs(factor), spacings.length - 1);
if (Number.isInteger(factor)) {
return spacings[factorAbs] * sign;
}
return interpolate(factorAbs, spacings) * sign;
},
});
const interpolate = (value: number, array: readonly number[]) => {
const floor = Math.floor(value);
const ceil = Math.ceil(value);
const diff = value - floor;
return array[floor] + (array[ceil] - array[floor]) * diff;
};
该函数方案同时处理了 'auto'、负数(sign 取反)、小数(interpolate 线性插值)和越界(Math.min 钳制到最大索引),从源码角度正好对应 createUnaryUnit 中对数组形态的全部限制场景。
Multiple arity:0 到 4 个参数的多参数签名
theme.spacing() 最多接受 4 个参数,参数个数对应 CSS 简写值的 top / right / bottom / left 语义,可以显著减少样板代码(继承自原文档的 diff 示例):
-padding: `${theme.spacing(1)} ${theme.spacing(2)}`, // '8px 16px'
+padding: theme.spacing(1, 2), // '8px 16px'
混合字符串参数同样受支持:
margin: theme.spacing(1, 'auto'), // '8px auto'
实现层面,createSpacing 中的核心逻辑是:
const args = argsInput.length === 0 ? [1] : argsInput;
return args
.map((argument) => {
const output = transform(argument);
return typeof output === 'number' ? `${output}px` : output;
})
.join(' ');
可以据此确认几个行为细节:
- 无参调用默认取 1:
theme.spacing()等价于theme.spacing(1),即8px(测试用例spacing()→'8px'已验证,见 createSpacing.test.ts); - 数值结果自动补
px,字符串结果原样保留,最终用空格 join,因此(1, 'auto')得到'8px auto'; - 超过 4 个参数会触发开发态警告
MUI: Too many arguments provided, expected between 0 and 4, got N(仅process.env.NODE_ENV !== 'production'时输出); - 递归保护:函数对象上打了
spacing.mui = true标记,createSpacing(spacing)传入已生成的 spacing 函数时会直接返回原函数,测试用例should support recursion专门覆盖了这一点。
此外,theme.spacing('16px') 这类纯 CSS 单位字符串会被原样返回(getValue / transformer 对 typeof propValue === 'string' 直接透传),测试中有 spacing('16px') → '16px'、spacing('1rem') → '1rem' 的用例佐证。
CSS 变量模式下的 spacing 输出
当使用 createTheme({ cssVariables: true }) 时,theme.spacing() 的输出不再是具体像素值,而是引用 CSS 变量的表达式,便于运行时通过覆盖 --mui-spacing 实现主题切换。从 createTheme.test.js 的断言可以确认:
// 默认
theme.spacing(1); // 'var(--mui-spacing, 8px)'
theme.spacing(2); // 'calc(2 * var(--mui-spacing, 8px))'
// spacing 为数组时,生成逐索引的变量数组
const theme = createTheme({ cssVariables: true, spacing: [0, 4, 8] });
theme.vars.spacing; // ['var(--mui-spacing-0, 0px)', 'var(--mui-spacing-1, 4px)', 'var(--mui-spacing-2, 8px)']
theme.spacing(1); // 'var(--mui-spacing-1, 4px)'
theme.spacing(-1); // 'calc(-1 * var(--mui-spacing-1, 4px))'
这与前面源码分析相吻合:createUnaryUnit 对以 var( 开头的字符串输入会生成 `calc(${val} * ${themeSpacing})`(见 spacing.ts),负值则包装为 calc(-1 * ...)。
与系统 m/p 简写属性的关系
theme.spacing() 的转换函数同时也是 @mui/system 中 margin / padding 简写属性(m、mt、mx、p、py、px、marginInline、paddingBlock 等)的取值转换器。在 spacing.ts 的 style 函数中:
const transformer = theme?.internal_cache?.unarySpacing ?? createUnarySpacing(theme);
margin / padding / spacing 三个 style 函数通过 marginKeys、paddingKeys(以及合并后的 spacingKeys)识别简写属性名,再用同一个 transformer 把数值因子转换为 CSS 值,并支持响应式断点(iterateBreakpoints)。因此你在 createTheme({ spacing: ... }) 中自定义的规则,会同步作用于 sx、Stack、Box 等所有使用 m/p 属性的场景;Stack 组件甚至直接从 createSpacing 引入 Spacing 类型(见 createStack.tsx)。
另外,adaptV4Theme.js 中也使用 createSpacing(inputTheme.spacing) 来重建 v4 主题对象,说明 v4→v5+ 迁移路径复用了同一套 spacing 机制。
参数校验与开发态警告汇总
结合源码,以下输入会触发开发态 console.error 警告(生产构建中不输出):
| 触发场景 | 警告信息(节选) | 源码位置 |
|---|---|---|
createSpacing 传入对象等非法类型 |
MUI: The \theme.spacing` value (...) is invalid. It should be a number, an array or a function.` |
createSpacing.test.ts / spacing.ts |
| 数组形态下传入小数 | MUI: The "theme.spacing" array type cannot be combined with non integer values. |
spacing.ts |
| 数组形态下索引越界 | MUI: The value provided (N) overflows. The supported values are: [...] |
spacing.ts |
theme.spacing() 参数超过 4 个 |
MUI: Too many arguments provided, expected between 0 and 4, got N |
createSpacing.ts |
实践建议与文件索引
- 常规项目保持默认 8px 即可;若需整体收紧或放宽间距,优先用数字(
spacing: 4),这是最简单的形态; - 若项目遵循 rem/Bootstrap 风格单位,用函数形态
(factor) =>${0.25 * factor}rem``; - 若需要非线性的离散间距档位(如
[0, 4, 8, 16, 32, 64]),使用数组,但注意正整数索引的限制;必须使用小数、负数或混合字符串时,改用文档给出的插值函数方案; - 需要运行时切换间距主题(如明暗主题附带间距变化)时,考虑
cssVariables: true模式,theme.spacing()会输出var(...)/calc(... * var(...))表达式。
关键文件索引:
- 官方文档:docs/data/material/customization/spacing/spacing.md
- spacing 工具函数实现:packages/mui-system/src/createTheme/createSpacing.ts
- 单值转换与 m/p 系统属性:packages/mui-system/src/spacing/spacing.ts
- 主题创建中的 spacing 处理:packages/mui-material/src/styles/createThemeWithVars.js
- 单元测试:packages/mui-system/src/createTheme/createSpacing.test.ts、packages/mui-material/src/styles/createTheme.test.js
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 StartedRust0627
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