自定义插槽和子组件
了解如何覆盖 MUI X 组件的各个部分。
什么是插槽?
其中一些插槽允许您为 MUI X 组件提供您自己的 UI 原语。这是 Data Grid 组件上所有 baseXXX
组件(baseButton
、baseSelect
等)的作用。这些插槽接收的 props 应该尽可能通用,以便于与任何其他设计系统对接。
其他插槽允许您使用专门为此组件构建的自定义 UI 覆盖 MUI X UI 组件的各个部分。这是诸如 DateCalendar
组件上的 calendarHeader
或 Rich Tree View 组件上的 item
等插槽的作用。这些插槽接收特定于 UI 这部分的 props,并且很可能不会在您的应用程序中重复使用。
基本用法
如何覆盖插槽?
您可以通过为 slots
prop 提供自定义组件来覆盖插槽
如何自定义插槽?
您可以使用 slotProps
prop 将 props 传递给任何插槽
您也可以在同一组件上同时使用 slots
和 slotProps
大多数插槽还支持 slotProps
的回调版本。此回调接收一个对象,其中包含有关组件当前状态的信息,该信息可能因所使用的插槽而异
正确用法
插槽是一个 React 组件;因此,它应该在两次渲染之间保持相同的 JavaScript 引用。如果组件的 JavaScript 引用在两次渲染之间发生变化,React 将重新挂载它。您可以通过不在 slots
prop 中内联组件定义来避免这种情况。
以下前两个示例是有 bug 的,因为日历 header 会在每次击键后重新挂载,从而导致焦点丢失。
// ❌ The `calendarHeader` slot is re-defined each time the parent component renders,
// causing the component to remount.
function MyApp() {
const [name, setName] = React.useState('');
return (
<DateCalendar
slots={{
calendarHeader: () => (
<input value={name} onChange={(event) => setName(event.target.value)} />
),
}}
/>
);
}
// ❌ The `calendarHeader` slot is re-defined each time `name` is updated,
// causing the component to remount.
function MyApp() {
const [name, setName] = React.useState('');
const CustomCalendarHeader = React.useCallback(
() => <input value={name} onChange={(event) => setName(event.target.value)} />,
[name],
);
return <DateCalendar slots={{ calendarHeader: CustomCalendarHeader }} />;
}
// ✅ The `calendarHeader` slot is defined only once, it will never remount.
const CustomCalendarHeader = ({ name, setName }) => (
<input value={name} onChange={(event) => setName(event.target.value)} />
);
function MyApp() {
const [name, setName] = React.useState('');
return (
<DateCalendar
slots={{ calendarHeader: CustomCalendarHeader }}
slotProps={{ calendarHeader: { name, setName } }}
/>
);
}
TypeScript 的用法
类型化自定义插槽
如果您想确保自定义插槽组件的类型安全,可以使用 PropsFromSlot
接口声明您的组件
function CustomCalendarHeader({
currentMonth,
}: PropsFromSlot<DateCalendarSlots<Dayjs>['calendarHeader']>) {
return <div>{currentMonth?.format('MM-DD-YYYY')}</div>;
}
使用附加 props
如果您正在将附加 props 传递给您的插槽,您可以将它们添加到您的自定义组件接收的 props 中
interface CustomCalendarHeaderProps
extends PropsFromSlot<DateCalendarSlots<Dayjs>['calendarHeader']> {
displayWeekNumber: boolean;
setDisplayWeekNumber: (displayWeekNumber: boolean) => void;
}
然后,您可以在自定义组件中使用这些 props,并访问由宿主组件提供的 props 和您添加的 props
function CustomCalendarHeader({
displayWeekNumber,
setDisplayWeekNumber,
...other
}: CustomCalendarHeaderProps) {
return (
<Stack>
<DisplayWeekNumberToggle
value={displayWeekNumber}
onChange={setDisplayWeekNumber}
/>
<PickersCalendarHeader {...other} />
</Stack>
);
}
如果您的自定义组件具有与默认组件不同的类型,则需要将其强制转换为正确的类型。如果您使用 slotProps
将附加 props 传递给自定义组件,则可能会发生这种情况。如果我们以 calendarHeader
插槽为例,您可以按如下所示强制转换您的自定义组件
function MyApp() {
const [displayWeekNumber, setDisplayWeekNumber] = React.useState(false);
return (
<DateCalendar
displayWeekNumber={displayWeekNumber}
slots={{
calendarHeader:
CustomCalendarHeader as DateCalendarSlots<Dayjs>['calendarHeader'],
}}
slotProps={{
calendarHeader: {
displayWeekNumber,
setDisplayWeekNumber,
} as DateCalendarSlotProps<Dayjs>['calendarHeader'],
}}
/>
);
}
使用模块增强
如果您正在使用 Data Grid 包之一,您还可以使用模块增强来让 TypeScript 知道您的自定义 props
declare module '@mui/x-data-grid' {
interface ToolbarPropsOverrides {
name: string;
setName: (name: string) => void;
}
}
然后,您可以无需任何类型转换即可使用您的自定义插槽
function CustomToolbar({ name, setName }: PropsFromSlot<GridSlots['toolbar']>) {
return <input value={name} onChange={(event) => setName(event.target.value)} />;
}
function MyApp() {
const [name, setName] = React.useState('');
return (
<DataGrid
rows={[]}
columns={[]}
slots={{ toolbar: CustomToolbar }}
slotProps={{
toolbar: { name, setName },
}}
/>
);
}
有关更多详细信息,请参阅Data Grid - 自定义插槽和子组件——带有 TypeScript 的自定义插槽 props。
X 组件的插槽
您可以在每个组件的 API 文档中找到插槽列表(例如 DataGrid、DatePicker、BarChart 或 RichTreeView)。
大多数插槽都有一个独立的文档示例来展示如何使用它们