首页
/ MUI System 自定义组件样式化实战:unstable_styleFunctionSx 与独立样式函数深度解析

MUI System 自定义组件样式化实战:unstable_styleFunctionSx 与独立样式函数深度解析

2026-09-06 17:59:30作者:冯梦姬Eddie

本文基于 MUI 官方文档《Custom components》(位于 docs/data/system/getting-started/custom-components/custom-components.md)展开,讲解如何为完全自定义的(非 MUI 的)React 组件添加 sx 属性支持:一是通过 unstable_styleFunctionSx 工具函数,以比 Box 组件更小的包体积获得完整的 sx 能力;二是按需单独引入 palettespacing 等独立样式函数,将 MUI System 的样式能力"移植"到你自己的 styled 组件上。读完本文,你将掌握两种方案的完整可用代码、sx 样式函数在仓库源码中的真实执行链路,以及 unstable_createStyleFunctionSxunstable_extendSxProp 等进阶 API 的用法。

背景:为什么自定义组件需要 sx 支持

MUI System 的常规用法是在组件树根部使用 Box 组件,并通过 sx 属性编写样式(对应文档 docs/data/system/getting-started/the-sx-prop/the-sx-prop.mddocs/data/system/getting-started/usage/usage.md)。但当你基于 styled-components@emotion/styled 自己封装了一个完全自定义的组件(例如一个业务设计系统中的 CardButton)时,直接给 Box 套壳既笨重又违背了"完全自定义"的初衷。

官方文档给出了两条路线:

  1. unstable_styleFunctionSx 工具函数:把 sx 属性"注入"到你自己的 styled 组件上,功能与 Boxsx 完全一致,但包体积更小(不需要引入 Box 组件本身);
  2. 独立样式函数(standalone style functions):如果你只需要 sx 中的某几个样式能力(比如只要 color/bgcolorp/m 间距),可以单独 import 对应的样式函数,拿到最小的包体积。

下面结合仓库中的演示代码与 @mui/system 源码逐一深入。

方案一:用 unstable_styleFunctionSx 给自定义组件注入 sx 属性

完整示例(TypeScript 版)

以下代码取自仓库文档演示文件 StyleFunctionSxDemo.tsx,可以直接复制到项目中运行:

import styled, { ThemeProvider, StyleFunction } from 'styled-components';
import { unstable_styleFunctionSx, SxProps } from '@mui/system';
import { createTheme } from '@mui/material/styles';

interface DivProps {
  sx?: SxProps;
}

const theme = createTheme();

const Div = styled('div')<DivProps>(
  unstable_styleFunctionSx as StyleFunction<DivProps>,
);

export default function StyleFunctionSxDemo() {
  return (
    <ThemeProvider theme={theme}>
      <Div sx={{ m: 1, p: 1, border: 1 }}>Custom component with the sx prop</Div>
    </ThemeProvider>
  );
}

JavaScript 版本(见 StyleFunctionSxDemo.js)更简单,无需泛型断言:

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

const theme = createTheme();

const Div = styled('div')(unstable_styleFunctionSx);

export default function StyleFunctionSxDemo() {
  return (
    <ThemeProvider theme={theme}>
      <Div sx={{ m: 1, p: 1, border: 1 }}>Custom component with the sx prop</Div>
    </ThemeProvider>
  );
}

示例中的三个关键点

  1. styled('div') 传入的是样式函数而非 CSS 模板unstable_styleFunctionSx 本身就是一个 styled-components 风格的 StyleFunction(签名见下文类型定义),它读取 props.sx 并直接编译出 CSS 对象。
  2. 必须提供主题上下文。演示用 createTheme() 创建主题,并通过 styled-components 的 ThemeProvider 注入。注意演示文件中 ThemeProvider 是从 styled-components 包导入的——从源码看,该演示基于 styled-components 引擎;如果你的项目使用 emotion 引擎,则应使用 @mui/system(内部 re-export)或 @mui/styled-engine 提供的 ThemeProvider
  3. border: 1 这类值会被主题解析m: 1p: 1 走 spacing 映射,border: 1 会被 borderTransform 转换为 1px solid theme.palette.divider,这正是 MUI sx 相对原生 CSS 的核心价值。

源码深潜:sx 样式函数是如何工作的

unstable_styleFunctionSxpackages/mui-system/src/index.js 中随一组相关 API 一起导出:

export {
  default as unstable_styleFunctionSx,
  unstable_createStyleFunctionSx,
  extendSxProp as unstable_extendSxProp,
  unstable_defaultSxConfig,
} from './styleFunctionSx';

核心实现在 packages/mui-system/src/styleFunctionSx/styleFunctionSx.js。它由工厂函数 unstable_createStyleFunctionSx() 创建,默认导出即是一个开箱即用的实例。其执行逻辑可以概括为:

  • 入口判断:没有 props.sx 时直接返回 null,即组件不产生任何样式开销;
  • 主题与配置解析const config = theme.unstable_sxConfig ?? defaultSxConfig——你可以用主题的 unstable_sxConfig 字段替换/扩展默认的属性映射表,这是自定义 sx 属性名(如 sizebg)的官方扩展点;
  • 响应式与断点处理:对每个 sx 键值,通过 hasBreakpoint / iterateBreakpoints 判断值是否为断点对象或数组(如 p: { xs: 1, sm: 2 }p: [1, 2]),并输出到对应的 media query 桶中;
  • 主题值转换setThemeValue 依据配置项的 themeKey / transform / style 将值映射到主题(如 p: 1padding: 8px),最终结果还会经过 removeUnusedBreakpoints 剔除空断点,并支持容器查询排序(sortContainerQueries)与 CSS 层(@layer sx,当 theme.modularCssLayers 开启时);
  • 嵌套选择器:非主题键的对象值(如 ':hover''& .child')会递归调用自身处理,因此伪类与嵌套选择器天然可用;
  • 数组输入sx 支持数组形式,实现上直接 sx.map(process) 逐条编译;
  • filterProps 约定styleFunctionSx.filterProps = ['sx']第 83 行),明确声明 sx 属性不会被转发到 DOM 节点,避免 React 的 unknown prop 警告。

类型定义位于 packages/mui-system/src/styleFunctionSx/styleFunctionSx.d.ts,其中:

  • SxProps<Theme>L71-L76)即 sx 的输入类型,可以是样式对象、(theme) => SystemStyleObject 函数,或二者的数组——这也解释了为什么 Div 的 Props 只需声明 sx?: SxProps
  • StyleFunctionSx 接口(L78-L81)形如 (props: object) => CSSObject,带可选 filterProps,与 styled-components 的 StyleFunction 完全同构,所以 TS 版示例中 unstable_styleFunctionSx as StyleFunction<DivProps> 的断言才能成立。

默认的属性映射表 defaultSxConfigpackages/mui-system/src/styleFunctionSx/defaultSxConfig.ts)决定了 sx 中哪些键会被"主题化":border* 系列走 borders + borderTransformcolor/bgcolorpalette + paletteTransformbgcolor 通过 cssProperty: 'backgroundColor' 映射到标准 CSS 属性)、p/pt/px/padding 等间距属性绑定 style: padding 样式函数等。这也就是说,unstable_styleFunctionSxBoxsx 共用同一套解析管线,二者行为一致。

进阶:扩展与定制

同一模块还提供三个进阶 API(均在 packages/mui-system/src/index.js 导出):

  • unstable_createStyleFunctionSx(styleFunctionMapping):传入自己的属性映射表创建新的 sx 样式函数,可用于实现自定义属性名或裁剪属性集;
  • unstable_defaultSxConfig:即上面的默认映射表,可深拷贝后修改,再配合主题上的 unstable_sxConfig 注入;
  • unstable_extendSxProp:实现在 packages/mui-system/src/styleFunctionSx/extendSxProp.ts,作用是把 props 上散落的系统属性(如直接传的 pbgcolor)拆分出来并合并进 sx,从而让你的 styled 组件既支持 sx 又支持"系统属性直接当 prop 用"的写法。它同样尊重 theme.unstable_sxConfig 来判断哪些是系统属性,且兼容 sx 为数组或函数的形式。

需要注意 unstable_ 前缀的含义:这是 MUI 仓库对处于演进中 API 的命名约定(参见仓库 CONTRIBUTING.md 中的 API 命名惯例),表示接口可能在后续版本中调整,生产使用前建议锁定大版本并关注 changelog。

方案二:按需引入独立样式函数,追求最小包体积

如果你不需要完整的 sx 语义(响应式对象、主题嵌套、unstable_sxConfig 等),而只想让自定义 styled 组件支持少量 MUI 属性,可以单独 import 对应的样式函数。仓库演示 CombiningStyleFunctionsDemo.tsx 展示了如何把 palettespacing 两个函数组合进一个 styled 组件:

import styled from 'styled-components';
import { palette, PaletteProps, spacing, SpacingProps } from '@mui/system';

const Div = styled.div<PaletteProps & SpacingProps>`
  ${palette}
  ${spacing}
`;

export default function CombiningStyleFunctionsDemo() {
  return (
    <Div color="white" bgcolor="palevioletred" p={1}>
      Styled components
    </Div>
  );
}

JavaScript 版本(见 CombiningStyleFunctionsDemo.js)去掉类型注解即可:

import styled from 'styled-components';
import { palette, spacing } from '@mui/system';

const Div = styled.div`
  ${palette}
  ${spacing}
`;

可用的独立样式函数清单

这些样式函数全部从 @mui/system 顶层导出(见 packages/mui-system/src/index.js),每个都对应一个可独立引入的样式函数模块:

导入名 目录 支持的属性(示例)
palette palette colorbgcolorborderColor
spacing spacing pmptmx 等,以及 padding/margin 全名
borders borders borderborderRadius
sizing sizing widthheightmaxWidth
flexbox flexbox displayalignItemsgap
grid(cssGrid) cssGrid display: 'grid'gap/rowGap/columnGap
positions positions positiontopzIndex
shadows shadows boxShadow
typography typography fontSizefontWeightfontFamilylineHeight
display display displayoverflowvisibility

这些函数同时也是 unstable_styleFunctionSx 内部拼装 sx 属性的"零件"——defaultSxConfigdefaultSxConfig.ts)正是把 paddingmarginborderRadiuspaletteTransformsizingTransform 等函数/变换注册进映射表,由 sx 管线统一调度。二者本质同源,差异在于:独立引入时你在 styled 组件上获得的是扁平的 prop 支持(如直接 p={1}),而 sx 方案提供统一的嵌套/响应式/主题回调入口

此外,@mui/system 还导出了 compose 工具,可用于显式合并多个样式函数;以及 stylestyle(props, theme, styleFunctionMapping)),当你需要完全手写属性到函数的映射时,它是 sx 管线之下的最底层原语。

两种方案如何选择

  • 想要与 Box 完全一致的 sx 体验(响应式数组/对象、伪类嵌套、(theme) => ... 回调),且组件本身是 styled 实现:选 unstable_styleFunctionSx,包体积上省去 Box 组件层;
  • 只需要少数几个属性(典型如 color + bgcolor + p),追求极致 bundle 大小:选独立样式函数,按上表只引入需要的模块;
  • 需要自定义 sx 属性名或裁剪属性集:用 unstable_createStyleFunctionSx 自定义映射表,或把修改后的 defaultSxConfig 副本挂到 theme.unstable_sxConfig
  • 同时需要"系统属性当 prop"与 sx:用 unstable_extendSxProp 在组件入口处做一次 props 归一化。

参考路径索引

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