自定义 MUI Base 组件
自定义 MUI Base 组件有几种方法,从应用自定义 CSS 规则到使用 Hook 构建完全自定义的组件。
使用 MUI Base,您可以自由决定要自定义组件结构和样式的程度。
组件样式化
本节回顾了几种可用的自定义方法:应用自定义 CSS 规则、覆盖默认子组件插槽、自定义插槽 props 以及使用 Hook 构建完全自定义的组件。
选择哪个选项?
多种选项可能会让人感到不知所措,尤其是当您是 MUI Base 的新手时。那么,如何决定使用哪一个呢?
首先要做的决定是使用非样式化组件还是 Hook。Hook 更适合制作可以进一步自定义的组件库。例如,我们自己的 Joy UI 就是使用 MUI Base 的 Hook 实现的。Hook 也作为几个 Material UI 组件的基础,并且该库的未来版本将更广泛地使用它们。
如果您不需要使您的组件库可自定义(例如,通过暴露 slotProps),那么非样式化组件由于其简洁性可能是一个更好的选择。
在选择非样式化组件之后,还需要做一个决定:如何对其进行样式化。答案取决于项目中使用的样式解决方案
Plain CSS, Sass, Less
...或任何其他编译为 CSS 的东西
您可以使用内置类来样式化组件,或者指定您自己的类并在样式表中引用它们。
CSS Modules
当使用 CSS Modules 时,最简单的方法是使用 slotProps 指定自定义类,如下所示
import clsx from 'clsx';
import { Switch as BaseSwitch, SwitchOwnerState } from '@mui/base/Switch';
import classes from './styles.module.css';
export default function Switch(props) {
const slotProps = {
root: (ownerState: SwitchOwnerState) => ({
className: clsx(classes.root, {
[classes.checked]: ownerState.checked,
[classes.disabled]: ownerState.disabled,
}),
}),
thumb: { className: classes.thumb },
track: { className: classes.track },
input: { className: classes.input },
};
return <BaseSwitch {...props} slotProps={slotProps} />;
}
在这个例子中,我们使用 clsx 实用程序来减少有条件地应用类名称所需的工作量。
Tailwind CSS
使用 slotProps 来应用自定义样式,使用 Tailwind CSS,如下所示
import { Switch as BaseSwitch, SwitchOwnerState } from '@mui/base/Switch';
export default function Switch(props) {
const slotProps = {
root: (ownerState: SwitchOwnerState) => ({
className: `inline-block w-8 h-5 rounded-full cursor-pointer relative ${
ownerState.checked ? 'bg-cyan-500' : 'bg-zinc-400'
}`,
}),
thumb: (ownerState: SwitchOwnerState) => ({
className: `bg-white block w-3.5 h-3.5 rounded-full relative top-[3px] ${
ownerState.checked ? 'left-[3px]' : 'left-[14px]'
}`,
}),
input: { className: 'absolute w-full h-full inset-0 opacity-0 z-10 m-0' },
};
return <BaseSwitch {...props} slotProps={slotProps} />;
}
有关集成 MUI Base 和 Tailwind CSS 的更多信息,请参阅我们的使用 Tailwind CSS 指南。
Styled components
如果您将 CSS-in-JS 解决方案与类似 styled-components 的 API(例如 MUI System 或 Emotion)一起使用,最好的方法是使用 slots 属性提供样式化的子组件,如 下面的演示所示。
或者,您可以将整个非样式化组件包装在 styled 实用程序中,并使用 CSS 类定位各个子组件
应用自定义 CSS 规则
如果您对组件渲染的 HTML 的默认结构感到满意,则可以将自定义样式应用于组件的类。
每个组件都有自己的一组类。有些类是静态的,也就是说它们始终存在于组件上。另一些类是有条件地应用的——例如 base--disabled,它仅在组件被禁用时存在。
每个组件的 API 文档都列出了该组件使用的所有类。此外,您可以导入一个 [componentName]Classes 对象,该对象描述给定组件使用的所有类,如下面的演示所示
如果您不使用这些类,您可以禁用它们来清理 DOM。有关说明,请参阅禁用默认 CSS 类。
覆盖子组件插槽
如果您想更改组件渲染的 HTML 结构,可以使用 slots 和/或 component 属性覆盖默认子组件(“插槽”)——有关更多详细信息,请参阅 Base 用法页面上的“共享属性”。
以下演示使用 Switch 来展示如何通过将样式应用于其三个子组件插槽来创建样式化组件:root、thumb 和 input。
请注意,虽然此演示使用 MUI System 作为样式解决方案,但您可以自由选择任何替代方案。
您在 slots 属性中传入的组件从顶级组件(“所有者”)接收 ownerState 属性。按照惯例,它包含传递给所有者的所有属性,并与其渲染状态合并。
例如
<Switch slots={{ thumb: MyCustomThumb }} data-foo="42" />
在这种情况下,MyCustomThumb 组件接收具有以下数据的 ownerState 对象
{
checked: boolean;
disabled: boolean;
focusVisible: boolean;
readOnly: boolean;
'data-foo': string;
}
您可以使用此对象来样式化您的组件。
如果您需要使用 ownerState 将某些属性传播到第三方组件,则必须为此目的创建一个自定义包装器。但是,如果您不需要 ownerState 并且只想解决错误,则可以使用 prepareForSlot 实用程序
自定义插槽 props
使用 slotProps 属性自定义内部组件 props。最常见的用例是设置类名,但您可以设置任何 prop,包括事件处理程序。
以下示例展示了如何向 Switch 的两个插槽添加自定义类
function Switch(props: SwitchProps) {
const slotProps: SwitchProps['slotProps'] = {
thumb: {
className: 'my-thumb',
},
track: {
className: 'my-track',
},
};
return <Switch {...props} slotProps={slotProps} />;
}
switch:thumb 和 switch:class 是无条件添加的——它们将始终存在于 Switch 组件上。
您可能需要在组件处于特定状态时才应用类。一个很好的例子是根据 Switch 的选中状态向其添加 on 和 off 类,如下面的演示所示
在这里,根插槽接收的是回调函数,而不是带有 props 的对象。它的唯一参数是 ownerState,它是一个描述“所有者组件”(在本例中为 Switch)状态的对象。ownerState 保存所有者组件的所有 props(在适用情况下应用默认值),并使用组件的内部状态进行增强。在 Select 组件的情况下,附加信息包括 checked、disabled、focusVisible 和 readOnly 布尔字段。
使用 Hook 创建自定义组件
如果您需要完全控制组件渲染的 HTML 结构,可以使用 Hook 构建它。
Hook 使您可以访问组件使用的逻辑,但没有任何默认结构。有关更多详细信息,请参阅 Base 用法页面上的“组件 vs. Hook”。
Hook 返回组件的当前状态(例如 checked、disabled、open 等),并提供返回 props 的函数,您可以将这些 props 应用于完全自定义的组件。
对于 Switch,该组件附带 useSwitch Hook,它为您提供所有功能,而没有任何结构。
它返回以下对象
{
checked: Readonly<boolean>;
disabled: Readonly<boolean>;
readOnly: Readonly<boolean>;
focusVisible: Readonly<boolean>;
getInputProps: (otherProps?: object) => SwitchInputProps;
}
checked、disabled、readOnly 和 focusVisible 字段表示开关的状态。使用它们将样式应用于您的 HTML 元素。
getInputProps 函数可用于获取放置在 HTML <input> 上的 props,以使开关可访问。
禁用默认 CSS 类
如果您不需要组件上的内置类,可以禁用它们。这将清理 DOM,并且在您应用自己的类或使用 CSS-in-JS 解决方案样式化组件时尤其有用。为此,请将您的组件包装在 ClassNameConfigurator 组件中(从 @mui/base/utils 导入)
<ClassNameConfigurator disableDefaultClasses>
<Button>I'm classless!</Button>
</ClassNameConfigurator>
检查以下演示中的元素以查看差异