首页
/ Material UI theme.spacing() 间距系统详解:从 8px 缩放因子到源码级实现原理

Material UI theme.spacing() 间距系统详解:从 8px 缩放因子到源码级实现原理

2026-09-06 11:53:15作者:仰钰奇

本文基于 Material UI 官方自定义文档 Spacing 展开,讲解 theme.spacing() 间距工具函数的默认机制、数字 / 函数 / 数组三种自定义配置方式、多参数(Multiple arity)签名,以及字符串混用规则;并结合仓库中 packages/mui-systempackages/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,有几个从源码可以直接确认的行为细节:

  1. 取绝对值作为索引const abs = Math.abs(val)theme.spacing(2)themeSpacing[2]
  2. 支持负数取反:负值会返回相反符号的结果——数值结果直接取负,CSS 变量字符串(var(...) 开头)会被包装成 calc(-1 * var(...)),普通字符串则加 - 前缀;
  3. 开发态校验:若传入非整数(如 0.5),会打印 MUI: The "theme.spacing" array type cannot be combined with non integer values. 警告;若索引越界(如数组只有 6 项却传 6),会打印 MUI: The value provided (6) overflows. 并列出受支持的范围;
  4. 字符串透传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(' ');

可以据此确认几个行为细节:

  • 无参调用默认取 1theme.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 简写属性(mmtmxppypxmarginInlinepaddingBlock 等)的取值转换器。在 spacing.tsstyle 函数中:

const transformer = theme?.internal_cache?.unarySpacing ?? createUnarySpacing(theme);

margin / padding / spacing 三个 style 函数通过 marginKeyspaddingKeys(以及合并后的 spacingKeys)识别简写属性名,再用同一个 transformer 把数值因子转换为 CSS 值,并支持响应式断点(iterateBreakpoints)。因此你在 createTheme({ spacing: ... }) 中自定义的规则,会同步作用于 sxStackBox 等所有使用 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(...)) 表达式。

关键文件索引:

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388