跳至主要内容

MUI Sandbox @ github

MUI 架構

Understanding MUI packages @ MUI v5

MUI Core

  • Material UI(@mui/material):使用基於 Material Design 所設計出來的元件,同時包含「功能」和「樣式」。
  • MUI Base(@mui/base):包含許多 Unstyled(headless)的 React UI 元件,只是用提供的「功能」,CSS 樣式的部分需要自己處理。
  • MUI System(@mui/system):相對於只用「功能」的 MUI Base,MUI System 則是提供許多 CSS Utilities 可以使用,特別像是 sx,它需要搭配 MUI Base、MUI components 使用。

MUI System

MUI System @ MUI 5

sx​

The sx prop @ MUI 5

MUI system 的 sx 特別適合用在一次性(one-ff)的元件。

一般使用​

到 Properties 可以找到所有可以用的屬性名稱,以及可以直接使用到的 theme mapping,並且看 ${value} 的地方。

舉例來說:

sx-properties

  • 如果寫 { border: 3 },表示的即是 border: 3px solid
  • 如果寫 { borderColor: 'success.dark' },對應的會是 theme.palette.success.dark 的顏色
  • 如果寫 { gap: 3 },對應到的是 theme.spacing(3)
  • 如果寫 { display: 'inline-block' },因為這裡沒有任何 mapping(none),所以就是直接帶入該值
  • 如果寫 { fontFamily: 'fontFamily' },因為它的 mapping 是 theme.typography[value],因此會套用 theme.typography.fontFamily,表示會套用預設的 font family。

一般來說,搭配 <Box /> 使用 sx 是最簡單的方式:

<Box
sx={{
border: 1, // 1px solid
bgcolor: 'background.paper', // bgcolor 的 "c" 是小寫,theme.palette.background.paper
boxShadow: 1, // theme.shadows[1]
borderRadius: 2, // theme.shape.borderRadius * 2
p: 2, // theme.spacing(2)
minWidth: 300, // 300px
fontFamily: 'fontFamily', // theme.typography.fontFamily
}}
/>

除了可以把上述的 System keys 放在 sx 中使用外,也可以把它們當成 <Box /> 的 props 帶入:

// 個人認為還是把 CSS 相關的屬性放在 `sx` 中集中管理比較好,和「功能」較相關的再用 props 傳下去。
<Box color="text.primary" fontSize={32} fontWeight="medium" fontFamily="fontFamily">
98.3 K
</Box>
把樣式相關的屬性集中管理在 sx 中

個人認為還是把 CSS 相關的屬性放在 sx 中集中管理比較好,和「功能」較相關的再用 props 傳下去。

callback value:需要使用到 theme 時​

需要的話,sx 的「值」或「屬性值」都可以帶 callback function,特別適合用在需要使用 theme 的時候:

// 屬性值使用 callback
<Box
sx={{
height: (theme) => theme.spacing(2),
}}
/>

// sx 的值使用 callback
<Box
sx={(theme) => ({
...theme.typography.body1,
color: theme.palette.primary.main
})}
/>

Array values:特定情況下才套用特定樣式​

<Box
sx={[
{
'&:hover': {
bgcolor: 'info.main',
color: 'info.contrastText',
},
},
checked && {
'&:hover': {
bgcolor: 'success.main',
color: 'success.contrastText',
},
},
]}
>
Array Values
</Box>

把 sx 當成 props 傳遞​

Passing the sx prop @ MUI 5

  • 定義型別的時候使用 SxProps<Theme>
// credit: https://mui.com/system/getting-started/the-sx-prop/#passing-the-sx-prop
import { SxProps, Theme } from '@mui/material/styles';

interface ListHeaderProps {
children: React.ReactNode;
sx?: SxProps<Theme>;
}

function ListHeader({ sx = [], children }: ListHeaderProps) {
return (
<ListItem
sx={[
{
width: 'auto',
textDecoration: 'underline',
},
// You cannot spread `sx` directly because `SxProps` (typeof sx) can be an array.
...(Array.isArray(sx) ? sx : [sx]),
]}
>
<FormLabel sx={{ color: 'inherit' }}>{children}</FormLabel>
</ListItem>
);
}

搭配 TypeScript 使用​

如果你想要把套用到 sx 的物件存成一個變數使用,你「可能」需要搭配 as const:

const style = {
bgcolor: 'background.paper', // bgcolor 的 "c" 是小寫
boxShadow: 1,
borderRadius: 2, // theme.shape.borderRadius * 1
p: 2,
minWidth: 300,
flexDirection: 'column',
} as const;

<Box sx={style} />;
資訊

筆者實測沒有使用 as const 也沒有發生型別上的錯誤(2022.10.30)。

客製化(Customization)

MUI Playground @ code sandbox

Theme​

資訊

createMuiTheme 是 material ui 4 的用法;createTheme 是 MUI 5 的用法。

查看 Default theme 可以知道所有可以覆蓋的預設樣式。

useTheme​

在元件中,可以使用 useTheme 來取得 theme,或是用 theme 中提供的 utility,例如 theme.spacing()。

修改預設主題樣式​

Theming @ MUI > Customization

使用 createTheme() 可以添加或修改預設的樣式,它會回傳一個新的 theme,接著只要把這個 theme 放到 <ThemeProvider> 中,即可套用新的主題樣式。

在 MUI 中,所有預設的樣式都列在 Default theme 中,可以找到欲改變的屬性並覆蓋即可。

舉例來說,如果想改變預設 primary 的樣式,在文件 Default theme 中可以看到它的屬性被放在 palette.primary 中:

MUI default theme

接著使用 createTheme 將該屬性的預設值覆蓋掉,並帶入 <ThemeProvider> 即可覆蓋掉預設的樣式:

import { orange } from '@mui/material/colors';
import { createTheme } from '@mui/material/styles';

export const theme = createTheme({
// 修改預設的樣式
palette: {
primary: {
main: orange[300],
// light: MUI 會自動根據 main 來推算顏色
dark: orange[500],
// contrastText: MUI 會自動根據 main 來推算顏色
},
},
});

export default function CustomStyles() {
return (
<ThemeProvider theme={theme}>
<CustomCheckbox defaultChecked />
</ThemeProvider>
);

在主題樣式中添加新的屬性(Custom variables)​

透過 createTheme 也可以在 theme 中添加新的屬性。

舉例來說,如果想要在 theme 中添加一個 status.danger 的屬性,可以直接把該屬性放入 theme 的物件中:

export const theme = createTheme({
// 添加新的屬性與樣式
status: {
danger: red[600],
},
});

但如果需搭配 TypeScript 使用的話,由於預設在 theme 中沒有這個屬性,因此型別檢查會有問題,這時候可以使用 TypeScript 中的 Module Augmentation 來擴增 Theme 的型別定義:

// 透過 Module Augmentation 來擴增 Theme 的型別定義
// 這裡會在 Theme 中增添 `status.danger`

declare module '@mui/material/styles' {
// 能夠在 theme 中找到這個屬性
interface Theme {
status: {
danger: React.CSSProperties['color']; // 或用 string
};
}
// 能夠透過 createTheme 新增屬性
interface ThemeOptions {
status: {
danger: React.CSSProperties['color']; // 或用 string
};
}
}

如此,就可以在 theme 中取得自訂義的樣式:

import { Checkbox, styled } from '@mui/material';
import { useTheme } from '@mui/material/styles';

const CustomCheckbox = styled(Checkbox)(({ theme }) => ({
color: theme.status.danger,
}));

function App() {
const theme = useTheme();

return (
<div className="App">
<CustomCheckbox />
<span>{theme.status.danger}</span>
</div>
);
}
注意

如果使用 VSCode 的 auto import 要特別留意!styled 是從 @mui/material import 的,而不是從 '@emotion/styled' import 的。

Palette​

在調色盤中的屬性可以在使用元件時,透過 color 來使用,包括新添加的屬性也可以。

例如:

<Button color="neutral" variant="contained">

修改調色盤中預設的色彩​

Palette @ mui

在 MUI 中預設的調色盤(palette)包含:

  • primary
  • secondary
  • error
  • warning
  • info
  • success

每個調色盤中又包含:

  • light
  • main
  • dark
  • contrastText

透過前面提到的「修改預設主題樣式」可以修改它們的預設值,很酷的是「對於我們沒指定的屬性,MUI 會自動根據 main 來計算它們的顏色」,例如,如果我們只提供 palette.xxx.main 的話,MUI 會自動推算出 light、dark 和 contrastText 的顏色(計算的邏輯可以參考:Providing the colors directly)。

在調色盤中添加新的配色​

Adding new colors @ MUI > Customization > Palette

一樣可以直接在 createTheme 的時候,將常用的配色添加到調色盤中:

export const theme = createTheme({
palette: {
// 添加新的配色
neutral: {
main: grey[500],
},
},
});

搭配 TypeScript 使用時,和修改 theme 一樣,可以透過 module augmentation 來擴增 TypeScript 的型別定義,主要是 Palette 和 PaletteOptions 避免 TS 找不到該屬性而報錯。

例如,這裡我們希望在添加 neutral 這個屬性在調色盤中:

import { grey } from '@mui/material/colors';
import { createTheme } from '@mui/material/styles';

declare module '@mui/material/styles' {
// 能夠在 palette 中找到這個屬性
interface Palette {
neutral: Palette['primary'];
}

// 能用使用 createTheme 來設定這個屬性
interface PaletteOptions {
neutral?: PaletteOptions['primary'];
}
}

export const theme = createTheme({
// 在調色盤中添加新的屬性
palette: {
neutral: {
main: grey[500],
// MUI 會根據 main 自動計算出 dark, light, contrastText 的顏色
},
},
});

另外由於使用 palette 的元件並不知道 color 中又新的屬性可以使用(雖然是可以直接使用的),因此,同樣要擴增該元件的型別定義。

例如,雖然可以直接用 <Button color="neutral">,但因為 Button 元件並不知道 color 有 neutral 可以用,所以 TS 會報錯,這時候可以擴增 ButtonPropsColorOverrides 的型別:

import Button from '@mui/material/Button';

declare module '@mui/material/Button' {
interface ButtonPropsColorOverrides {
neutral: true;
}
}

export default function CustomButton() {
return (
<Button color="neutral" variant="contained">
Neutral
</Button>
);
}

Typography​

Typography @ MUI

在 MUI 中,typography 共有 13 種不同的 variants,其中每個 variant 中可以調整的屬性可參考 Default theme:

  • h1 ~ h6
  • subtitle1, subtitle2
  • body1, body2
  • button:修改所有 Button 的文字樣式。
  • caption
  • overline

針對 TypeScript,如果希望增加 Typography 中的屬性,可以使用 module augmentation 來擴增 TypographyVariants 和 TypographyVariantsOptions 的型別;並且針對使用此 variant 的元件,使用 TypographyPropsVariantOverrides 來擴增。詳細作法參考官網的 Adding & disabling variants。

Spacing​

Spacing @ MUI

修改預設值​

MUI 預設的 spacing 的 base 是 8px,所以如果使用 theme.spacing(2) 表示 16px。

如果希望調整 spacing 的 base,一樣可以透過 createTheme 來修改,其中修改的參數可以是數值、函式、陣列等等,詳細差異與使用可直接參考官網。

theme.spacing:套用 margin 或 padding 時好用​

theme.spacing() 這個函式最多可以吃 4 個參數:

theme.spacing(2); // '16px'
theme.spacing(1, 2); // '8px 16px';
theme.spacing(1, 'auto'); // '8px auto';

breakpoints​

breakpoints @ MUI

修改預設值​

如果希望修改 breakpoints 的預設值,一樣是透過 createTheme 來進行覆蓋,同樣可以參考 Default theme 中看可以覆蓋的屬性。

theme.breakpoints.xxx():需要用 media query 時好用​

在 MUI 中,可以透過 theme.breakpoints.xxx() 來做到 media queries 的使用,其中包含 up、down、only、not、between 可以使用。

color​

color @ MUI

可以直接 import color 來使用,可以用的顏色列在這個 Color palette 中:

// https://mui.com/material-ui/customization/color/
import { purple, red } from '@mui/material/colors';

const primary = red[500]; // #f44336
const accent = purple['A200']; // #e040fb
const accent = purple.A200; // #e040fb (alternative method)

How to customize​

How to customize @ MUI

One-off customization​

sx​

  • 使用 sx

Overriding nested component style:使用 xxxClasses​

  • 找到 nested component 的 class 加以覆蓋,例如 className 如果是 span.MuiSlider-thumb.css-ae2u5c-MuiSlider-thumb::after,重點是這個 .MuiSlider-thumb,可以在 sx 中選到這個 class 加以覆蓋。
  • 在 material UI 的元件中,有提供 xxxClasses 可以 import 這個 className,以避免打錯字,例如,可以使用 sliderClasses 來針對 nested 的樣式進行覆蓋
// credit: https://mui.com/material-ui/customization/how-to-customize/#state-classes */

import { Slider, sliderClasses } from '@mui/material';

<Slider
defaultValue={30}
sx={{
width: 300,
color: 'success.main',
// 選到 nester component 的 class
// MuiSlider-thumb
[`& .${sliderClasses.thumb}`]: {
borderRadius: '1px',
},
}}
/>;

state classes​

針對 MUI 中的狀態(例如,active、checked、disabled、error、...),可以使用 MUI 提供的 state classes,但在使用的時候要記得只針對特定的元件去做覆蓋,不要直接改全域的樣式。

提示

同樣可以使用 xxxClasses 來取得該 className,避免手動打拼錯字。

/* credit: https://mui.com/material-ui/customization/how-to-customize/#state-classes */
/* ❌ NOT OK */
.Mui-error {
color: red;
}

/* ✅ OK */
.MuiOutlinedInput-root.Mui-error {
color: red;
}

Reusable Components​

使用 styled(),來針對可複用的元件來調整樣式:

// credit: https://mui.com/material-ui/customization/how-to-customize/#2-reusable-component

import Slider, { sliderClasses, SliderProps } from '@mui/material/Slider';
import { alpha, styled } from '@mui/material/styles';

interface StyledSliderProps extends SliderProps {
success?: boolean;
}

const StyledSlider = styled(Slider, {
// 把希望能被 forward 的 prop 暴露
shouldForwardProp: (prop) => prop !== 'success',
})<StyledSliderProps>(({ success, theme }) => ({
width: 300,
...(success && {
color: theme.palette.success.main,
[`& .${sliderClasses.thumb}`]: {
[`&:hover, &.${sliderClasses.focusVisible}`]: {
boxShadow: `0px 0px 0px 8px ${alpha(theme.palette.success.main, 0.16)}`,
},
},
[`&.${sliderClasses.active}`]: {
boxShadow: `0px 0px 0px 14px ${alpha(theme.palette.success.main, 0.16)}`,
},
}),
}));

export default StyledSlider;

Components​

theme-components @ MUI

default props​

如果想要修改元件中 props 的預設值,可以在 createTheme 中透過 defaultProps 來修改:

export const theme = createTheme({
// Components
components: {
// 元件的名稱
MuiButtonBase: {
defaultProps: {
// 把 Button 預設 ripple 的行為關掉
disableRipple: true,
},
},
},
});

global style overrides​

如果想要修改 MUI 中某一元件的樣式,可以使用 styleOverrides 這個屬性:

export const theme = createTheme({
components: {
// 元件的名稱
MuiButton: {
styleOverrides: {
// 元件的 slot
root: {
fontSize: 32,
},
},
},
},
});

Overrides based on props​

需要的話,透過 ownerState 可以取得該元件的 props,因此也可以根據 props 來套用不同的預設樣式:

export const theme = createTheme({
components: {
MuiSlider: {
styleOverrides: {
valueLabel: ({ ownerState, theme: _theme }) => ({
...(ownerState.orientation === 'vertical' && {
backgroundColor: 'transparent',
color: _theme.palette.grey[500],
fontWeight: 700,
padding: 0,
left: '3rem',
}),
[`&.${sliderClasses.valueLabelOpen}`]: {
transform: 'none',
top: 'initial',
},
}),
},
},
},
});

添加 variants​

Creating new component variants @ mui

LEGACY (v4)

[LEGACY] Customization​

[LEGACY] TS: 在 palette 中建立常用顏色​

Adding new colors @ Material UI > Customization > Palette

也可以直接在 createMuiTheme 的時候,透過 palette 這個屬性把常用的顏色設定進去。但若搭配 TypeScript 使用,需要額外對 module 添加定義,否則 TypeScript 會因為找不到該屬性而報錯:

// 在 palette 中多一個 status 屬性
declare module '@material-ui/core/styles/createPalette' {
interface Palette {
status: {
[key: string]: string;
};
}
interface PaletteOptions {
status: {
[key: string]: string;
};
}
}

const theme = createMuiTheme({
// ...
palette: {
text: {
white: 'white',
primary: '#616161', // rgb(97, 97, 97)
secondary: '#212121', // rgb(33, 33, 33)
hint: '#9e9e9e', // rgb(158, 158, 158)
},
// 客製化 status 的樣式
status: {
severe: '#a50f01', // 165, 15, 1
mild: '#cb8700', // 203, 135, 0
normal: '#424242', // 66, 66, 66
unknown: '#bdbdbd', // 189, 189, 189
},
},
});

如此一樣可以透過在 makeStyles 時透過 theme 取得這些色碼。

[LEGACY] 使用 className 覆蓋樣式​

一般來說,可以使用 makeStyles 搭配 useStyles 來覆蓋掉原本元件的樣式,例如:

import Button from '@material-ui/core/Button';
import { makeStyles } from '@material-ui/core/styles';

// STEP 1:使用 makeStyles 定義樣式
const useStyles = makeStyles((theme, props) => ({
root: {
backgroundColor: 'white',
},
}));

const StyledButton = () => {
// STEP 2:載入樣式
const classes = useStyles();

// STEP 3:透過 className 帶入樣式
return <Button className={classes.root}>My Button</Button>;
};

[LEGACY] 使用 classes 覆蓋樣式​

有些情況,對於階層比較深的元件,單純使用 className 可能無法覆蓋到底層的樣式,這時候可以 Material UI 元件中的 classes 屬性。在每一個 Material UI 的元件中,都會有該元件的 API 說明,在 API 文件中會有「CSS」的項目,告知可以哪些 CSS 是可以套用 classes 屬性來加以覆蓋。

舉例來說,在 Button API 中的 CSS 項目,有寫到 label:

Imgur

因此,就可以透過在 Button 元件的 classes 屬性中使用 label 來改變它的樣式:

<Button
classes={{
root: classes.root, // class name, e.g. `classes-nesting-root-x`
label: classes.label, // class name, e.g. `classes-nesting-label-x`
}}
>
My Button
</Button>

[LEGACY] Styled Components API​

在 Material UI 中也有內建的 Styled Component API:

import { styled } from '@material-ui/core/styles';

const MyComponent = styled('div')({
backgroundColor: 'red',
});

const MyThemeComponent = styled('div')(({ theme }) => ({
padding: theme.spacing(1),
}));

Styled API 中單純只要用 props​

若在 Styled API 中單純只要使用到該 component 的 props 的話,在後面的 () 中可以直接回傳物件,並在物件中透過 callback 的方式取出 props 來用:

interface MyButtonProps {
color: 'red' | 'blue';
}

// 第二個 () 中直接回傳物件,並在其中使用 props
const StyledTableCell = styled(TableCell)({
color: (props: MyButtonProps) => {
console.log('props', props);
return props.color as string;
},
});

<StyledTableCell color="red" />;

Styled API 中同時使用 props 和 theme​

⚠️ 這種做法還沒嘗試成功,雖然可以成功拿到 props 的資料,但 React 會一直出現警告:React does not recognize the foo prop on a DOM element.

但如果不只是要使用 props 同時也需要使用 theme 的話,寫法不太一樣:

  • 在第一個 () 中,需要把客製化的 props 拿出來(在 TypeScript),把剩下的傳回原本的元件中
  • 在第二個 () 中,需要帶入回傳物件的函式,而在該函式的參數中可以取得該元件的 props 和 theme。同時,不能在回傳的物件中使用 console.log():
// 第二個 () 中直接帶入函式,並透過函式的參數可以取得元件上的 props 和 theme
const StyledTableCell = styled(({ foo, ...other }) => <TableCell {...other} />)((props) => {
const { foo, theme } = props as any;
return {
color: foo,
};
});

<StyledTableCell foo="bar" />;

最後還是用回較單純的 Hook API 用法。

[LEGACY] Hook API​

在 TypeScript 中需要先把該元件對應的 Type 載入,例如,這裡使用 <TableCell> 元件,則需要從同一個位置載入 TableCellProps 這個 Type,若沒有載入這個 Type 又使用了 material ui 對元件提供的 props 時,TypeScript 會報錯:

import TableCell, { TableCellProps } from '@material-ui/core/TableCell';
import { Omit } from '@material-ui/types';

const useTableCellStyles = makeStyles((theme, props) => ({
tableCell: {
color: ({ abnormalStatus }: { abnormalStatus: AbnormalStatus }) => {
switch (abnormalStatus) {
case AbnormalStatus.Severe: {
return theme.palette.status.severe;
}
case AbnormalStatus.Mild: {
return theme.palette.status.mild;
}
case AbnormalStatus.Normal: {
return theme.palette.status.hint;
}
default: {
return theme.palette.status.hint;
}
}
},
},
}));

interface InterfaceStyledTableCell {
abnormalStatus: AbnormalStatus;
}

const StyledTableCell: React.FC<
InterfaceStyledTableCell & Omit<TableCellProps, keyof InterfaceStyledTableCell>
> = ({ abnormalStatus, children, ...other }) => {
const classes = useTableCellStyles({ abnormalStatus });
return (
<TableCell className={classes.tableCell} {...other}>
{children}
</TableCell>
);
};

<StyledTableCell abnormalStatus={AbnormalStatus.Unknown} />;

💡 之前嘗試不用 Omit 一樣能正常運作,像是這樣:React.FC<InterfaceStyledTableCell & TableCellProps。

[LEGACY] 建立可共用樣式的 className​

keywords: createStyles, makeStyles​

createStyles​

如果想要在 makeStyles 中使用 theme,可以從 makeStyles 的第一個參數取得,其中 createStyles 的用途主要是針對 TypeScript,在 runtime 時並沒有任何效果:

// https://material-ui.com/styles/api/#createstyles-styles-styles
import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';

const useStyles = makeStyles((theme: Theme, props) => createStyles({
root: {
backgroundColor: theme.color.red,
},
}));

export default function MyComponent {
const classes = useStyles();
return <div className={classes.root} />;
}

makeStyles​

// https://material-ui.com/styles/api/#makestyles-styles-options-hook
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles({
root: {
backgroundColor: 'red',
color: (props) => props.color,
},
});

export default function MyComponent(props) {
const classes = useStyles(props);
return <div className={classes.root} />;
}

[LEGACY] TS: 在 theme 中添加其他客製化屬性(custom properties in theme with TypeScript)​

theme 是直接套用在 CSS 的 value 而不能直接拿來當 className:

Customization of Theme @ Material UI > Guides > TypeScript

import { createMuiTheme } from '@material-ui/core';

declare module '@material-ui/core/styles/createMuiTheme' {
// 能夠在 theme 中找到這個屬性
interface Theme {
utils: {
[key: string]: number | string;
};
}
// 能夠透過 createMuiTheme 新增屬性
interface ThemeOptions {
utils?: {
[key: string]: {
[key: string]: number | string;
};
};
}
}

// 在主題中添加客製化樣式
const theme = createMuiTheme({
// spacing: 4,
// palette,
// typography,
utils: {
fz18: {
fontSize: 18,
}, // ...
},
});

使用時有時需要搭配 as string:

// 因為 backgroundColor 只能是 string,但 theme.utils.color.danger 的 type 是 string | number
// 所以要加上 as string
const useCardStyles = makeStyles((theme) => ({
cardHeader: {
backgroundColor: ({ abnormalStatus }: { abnormalStatus: AbnormalStatus }) => {
return theme.utils.color.danger as string
}
}
})

[LEGACY]在 makeStyles 中使用 animation​

Do keyframe animations work? @ Github Issues

在 makeStyles 中可以使用 animation,但是:

  • animation 需要定義在最外層
  • 使用 animation 的地方,animation name 前面要加上 $
const useStyles = makeStyles((theme) => ({
refreshButton: {
marginLeft: 15,
backgroundColor: theme.palette.grey[300],
color: theme.palette.text.secondary,
transitionDuration: '1s',
transitionProperty: 'transform',

// animation name 前面要加上 `$`
'& svg': {
animation: '$spin infinite 1.5s linear',
},
},
// @keyframes 要定義在最外層
'@keyframes spin': {
'0%': {
transform: 'rotate(360deg)',
},

'100%': {
transform: 'rotate(0deg)',
},
},
});

Source Code

mui-materials​

Material UI 的元件會放在 @material-ui/mui-materials。

mui-utils​

裡面放了許多好用的 utils

useForkRef​

useForkRef 可以接受兩個 ref(例如 refA、refB),並會回傳一個接受 refValue 的 function,當執行這個 function 後,會同時把 refValue 設定到這兩個 ref 中,舉例來說:

const refA = useRef();
const refB = useRef();
const handleRef = useForkRef<HTMLInputElement>(refA, refB);

// handleRef 被呼叫時,會把 "some value" 設定到 refA 和 refB 中
handleRef('some value');

在 material UI 中,會使用 useForkRef 來將某個 DOM Node 同時保存在 forwardRef 的 ref 和該元件內部的 ref 中:

import useForkRef from './hooks/useForkRef';

// 透過 forward ref 將該元件的 ref 傳到外面
const FocusableTextInput = forwardRef<HTMLInputElement>((props, ref) => {
// 該元件內部的 ref
const inputRef = useRef<HTMLInputElement | null>(null);

// handleRef 會是一個可以接受 value 的 function
const handleRef = useForkRef<HTMLInputElement>(inputRef, ref);

const handleClick = () => {
inputRef.current?.focus();
};

return (
<>
{/* handleRef 執行時,會同時把這個 inputElement 保存在 ref 和 inputRef 中*/}
<input type="text" ref={handleRef} />
<button type="button" onClick={handleClick}>
Click To Focus
</button>
</>
);
});