建立主題組件
了解如何建立完全自訂的組件,以接受您應用程式的主題。
簡介
Material UI 提供強大的主題功能,讓您可以將自己的組件新增至主題,並像使用內建組件一樣使用它們。
如果您正在 Material UI 之上建構組件庫,您可以按照下面的逐步指南建立一個自訂組件,該組件可以在多個專案中進行主題化。
或者,您可以使用提供的 範本 作為您的組件的起點。
逐步指南
本指南將引導您逐步建立此統計組件,該組件可以像內建的 Material UI 組件一樣接受應用程式的主題
1. 建立組件插槽
插槽讓您可以透過在主題的 styleOverrides 和 主題的 variants 中指定其各自的名稱來自訂組件的每個個別元素。
此統計組件由三個插槽組成
root
:組件的容器value
:統計數據的數字unit
:統計數據的單位或描述
使用帶有 name
和 slot
參數的 styled
API 建立插槽,如下所示
import * as React from 'react';
import { styled } from '@mui/material/styles';
const StatRoot = styled('div', {
name: 'MuiStat', // The component name
slot: 'root', // The slot name
})(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(0.5),
padding: theme.spacing(3, 4),
backgroundColor: theme.palette.background.paper,
borderRadius: theme.shape.borderRadius,
boxShadow: theme.shadows[2],
letterSpacing: '-0.025em',
fontWeight: 600,
}));
const StatValue = styled('div', {
name: 'MuiStat',
slot: 'value',
})(({ theme }) => ({
...theme.typography.h3,
}));
const StatUnit = styled('div', {
name: 'MuiStat',
slot: 'unit',
})(({ theme }) => ({
...theme.typography.body2,
color: theme.palette.text.secondary,
}));
2. 建立組件
使用上一步中建立的插槽組裝組件
// /path/to/Stat.js
import * as React from 'react';
const StatRoot = styled('div', {
name: 'MuiStat',
slot: 'root',
})(…);
const StatValue = styled('div', {
name: 'MuiStat',
slot: 'value',
})(…);
const StatUnit = styled('div', {
name: 'MuiStat',
slot: 'unit',
})(…);
const Stat = React.forwardRef(function Stat(props, ref) {
const { value, unit, ...other } = props;
return (
<StatRoot ref={ref} {...other}>
<StatValue>{value}</StatValue>
<StatUnit>{unit}</StatUnit>
</StatRoot>
);
});
export default Stat;
此時,您將能夠像這樣將主題應用於 Stat
組件
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
components: {
// the component name defined in the `name` parameter
// of the `styled` API
MuiStat: {
styleOverrides: {
// the slot name defined in the `slot` and `overridesResolver` parameters
// of the `styled` API
root: {
backgroundColor: '#121212',
},
value: {
color: '#fff',
},
unit: {
color: '#888',
},
},
},
},
});
3. 使用 ownerState 設定插槽樣式
當您需要設定基於插槽的 props 或內部狀態的樣式時,請將它們包裝在 ownerState
物件中,並將其作為 prop 傳遞給每個插槽。 ownerState
是一個特殊名稱,不會透過 styled
API 擴展到 DOM。
將 variant
prop 新增到 Stat
組件,並使用它來設定 root
插槽的樣式,如下所示
const Stat = React.forwardRef(function Stat(props, ref) {
+ const { value, unit, variant, ...other } = props;
+
+ const ownerState = { ...props, variant };
return (
- <StatRoot ref={ref} {...other}>
- <StatValue>{value}</StatValue>
- <StatUnit>{unit}</StatUnit>
- </StatRoot>
+ <StatRoot ref={ref} ownerState={ownerState} {...other}>
+ <StatValue ownerState={ownerState}>{value}</StatValue>
+ <StatUnit ownerState={ownerState}>{unit}</StatUnit>
+ </StatRoot>
);
});
然後,您可以在插槽中讀取 ownerState
,以根據 variant
prop 設定其樣式。
const StatRoot = styled('div', {
name: 'MuiStat',
slot: 'root',
- })(({ theme }) => ({
+ })(({ theme, ownerState }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(0.5),
padding: theme.spacing(3, 4),
backgroundColor: theme.palette.background.paper,
borderRadius: theme.shape.borderRadius,
boxShadow: theme.shadows[2],
letterSpacing: '-0.025em',
fontWeight: 600,
+ ...ownerState.variant === 'outlined' && {
+ border: `2px solid ${theme.palette.divider}`,
+ },
}));
4. 支援主題預設 props
若要為不同的專案自訂組件的預設 props,您需要使用 useThemeProps
API。
+ import { useThemeProps } from '@mui/material/styles';
- const Stat = React.forwardRef(function Stat(props, ref) {
+ const Stat = React.forwardRef(function Stat(inProps, ref) {
+ const props = useThemeProps({ props: inProps, name: 'MuiStat' });
const { value, unit, ...other } = props;
return (
<StatRoot ref={ref} {...other}>
<StatValue>{value}</StatValue>
<StatUnit>{unit}</StatUnit>
</StatRoot>
);
});
然後,您可以像這樣自訂組件的預設 props
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
components: {
MuiStat: {
defaultProps: {
variant: 'outlined',
},
},
},
});
TypeScript
如果您使用 TypeScript,則必須為組件 props 和 ownerState 建立介面
interface StatProps {
value: number | string;
unit: string;
variant?: 'outlined';
}
interface StatOwnerState extends StatProps {
// …key value pairs for the internal state that you want to style the slot
// but don't want to expose to the users
}
然後,您可以在組件和插槽中使用它們。
const StatRoot = styled('div', {
name: 'MuiStat',
slot: 'root',
})<{ ownerState: StatOwnerState }>(({ theme, ownerState }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(0.5),
padding: theme.spacing(3, 4),
backgroundColor: theme.palette.background.paper,
borderRadius: theme.shape.borderRadius,
boxShadow: theme.shadows[2],
letterSpacing: '-0.025em',
fontWeight: 600,
// typed-safe access to the `variant` prop
...(ownerState.variant === 'outlined' && {
border: `2px solid ${theme.palette.divider}`,
boxShadow: 'none',
}),
}));
// …do the same for other slots
const Stat = React.forwardRef<HTMLDivElement, StatProps>(function Stat(inProps, ref) {
const props = useThemeProps({ props: inProps, name: 'MuiStat' });
const { value, unit, variant, ...other } = props;
const ownerState = { ...props, variant };
return (
<StatRoot ref={ref} ownerState={ownerState} {...other}>
<StatValue ownerState={ownerState}>{value}</StatValue>
<StatUnit ownerState={ownerState}>{unit}</StatUnit>
</StatRoot>
);
});
最後,將 Stat 組件新增到主題類型。
import {
ComponentsOverrides,
ComponentsVariants,
Theme as MuiTheme,
} from '@mui/material/styles';
import { StatProps } from 'path/to/Stat';
type Theme = Omit<MuiTheme, 'components'>;
declare module '@mui/material/styles' {
interface ComponentNameToClassKey {
MuiStat: 'root' | 'value' | 'unit';
}
interface ComponentsPropsList {
MuiStat: Partial<StatProps>;
}
interface Components {
MuiStat?: {
defaultProps?: ComponentsPropsList['MuiStat'];
styleOverrides?: ComponentsOverrides<Theme>['MuiStat'];
variants?: ComponentsVariants['MuiStat'];
};
}
}
範本
此範本是上面逐步指南的最終產品,示範如何建構一個自訂組件,該組件可以使用主題設定樣式,就像它是內建組件一樣。