三、核心组件详解
3.1 按钮组件 Button
按钮是用户交互中最基础的组件,Material UI提供了丰富的按钮样式和状态。
import * as React from 'react';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import DeleteIcon from '@mui/icons-material/Delete';
import SendIcon from '@mui/icons-material/Send';
export default function BasicButtons() {
return (
<Stack direction="row" spacing={2}>
{/* 按钮变体 */}
<Button variant="text">文本按钮</Button>
<Button variant="contained">填充按钮</Button>
<Button variant="outlined">线框按钮</Button>
{/* 按钮颜色 */}
<Button variant="contained" color="primary">主要</Button>
<Button variant="contained" color="secondary">次要</Button>
<Button variant="contained" color="success">成功</Button>
<Button variant="contained" color="error">错误</Button>
<Button variant="contained" color="warning">警告</Button>
<Button variant="contained" color="info">信息</Button>
{/* 按钮尺寸 */}
<Button variant="contained" size="small">小</Button>
<Button variant="contained" size="medium">中</Button>
<Button variant="contained" size="large">大</Button>
{/* 带图标的按钮 */}
<Button variant="contained" startIcon={<DeleteIcon />}>删除</Button>
<Button variant="contained" endIcon={<SendIcon />}>发送</Button>
{/* 禁用和加载状态 */}
<Button variant="contained" disabled>禁用</Button>
<Button variant="contained" loading>加载中</Button>
{/* 全宽按钮 */}
<Button variant="contained" fullWidth>全宽按钮</Button>
</Stack>
);
}
3.2 主题提供者 ThemeProvider
ThemeProvider是Material UI主题系统的核心,它使用React Context将主题向下传递给所有子组件。
import * as React from 'react';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import Button from '@mui/material/Button';
import CssBaseline from '@mui/material/CssBaseline';
// 创建自定义主题
const theme = createTheme({
palette: {
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
},
typography: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
h1: {
fontSize: '2.5rem',
fontWeight: 600,
},
},
shape: {
borderRadius: 8,
},
});
function App() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Button variant="contained" color="primary">
主题按钮
</Button>
</ThemeProvider>
);
}
主题系统的核心优势:主题是响应式的,当主题值发生变化时,所有使用主题的组件都会自动更新。这得益于ThemeProvider使用React Context的机制。
3.3 表单组件
Material UI提供了完整的表单组件体系。
import * as React from 'react';
import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import InputLabel from '@mui/material/InputLabel';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import Switch from '@mui/material/Switch';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormLabel from '@mui/material/FormLabel';
export default function FormDemo() {
const [formData, setFormData] = React.useState({
name: '',
email: '',
role: '',
agree: false,
notifications: true,
gender: 'female',
});
const handleChange = (event) => {
const { name, value, checked } = event.target;
setFormData({
...formData,
[name]: event.target.type === 'checkbox' ? checked : value,
});
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('提交数据:', formData);
};
return (
<Box component="form" onSubmit={handleSubmit} sx={
{ maxWidth: 400, mx: 'auto', mt: 4 }}>
<TextField
fullWidth
margin="normal"
label="姓名"
name="name"
value={formData.name}
onChange={handleChange}
required
/>
<TextField
fullWidth
margin="normal"
label="邮箱"
name="email"
type="email"
value={formData.email}
onChange={handleChange}
required
/>
<FormControl fullWidth margin="normal">
<InputLabel>角色</InputLabel>
<Select
name="role"
value={formData.role}
onChange={handleChange}
label="角色"
>
<MenuItem value="admin">管理员</MenuItem>
<MenuItem value="user">普通用户</MenuItem>
<MenuItem value="guest">访客</MenuItem>
</Select>
</FormControl>
<FormControlLabel
control={
<Checkbox
name="agree"
checked={formData.agree}
onChange={handleChange}
/>
}
label="同意用户协议"
/>
<FormControlLabel
control={
<Switch
name="notifications"
checked={formData.notifications}
onChange={handleChange}
/>
}
label="接收通知"
/>
<FormControl component="fieldset" margin="normal">
<FormLabel component="legend">性别</FormLabel>
<RadioGroup
name="gender"
value={formData.gender}
onChange={handleChange}
row
>
<FormControlLabel value="female" control={<Radio />} label="女" />
<FormControlLabel value="male" control={<Radio />} label="男" />
</RadioGroup>
</FormControl>
<Button type="submit" variant="contained" fullWidth sx={
{ mt: 2 }}>
提交
</Button>
</Box>
);
}
3.4 数据展示组件
卡片 Card
卡片是Material UI中最常用的内容容器组件。
import * as React from 'react';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CardMedia from '@mui/material/CardMedia';
import Typography from '@mui/material/Typography';
import CardActions from '@mui/material/CardActions';
import Button from '@mui/material/Button';
export default function MediaCard() {
return (
<Card sx={
{ maxWidth: 345 }}>
<CardMedia
component="img"
height="140"
image="https://mui.com/static/images/cards/contemplative-reptile.jpg"
alt="绿鬣蜥"
/>
<CardContent>
<Typography gutterBottom variant="h5" component="div">
蜥蜴
</Typography>
<Typography variant="body2" color="text.secondary">
蜥蜴是蜥蜴亚目爬行动物的一个广泛群体,包含超过6000个物种,分布在南极洲以外的所有大陆。
</Typography>
</CardContent>
<CardActions>
<Button size="small">分享</Button>
<Button size="small">了解更多</Button>
</CardActions>
</Card>
);
}
表格 Table
import * as React from 'react';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
function createData(name, calories, fat, carbs, protein) {
return { name, calories, fat, carbs, protein };
}
const rows = [
createData('甜甜圈', 305, 3.7, 67, 4.3),
createData('纸杯蛋糕', 356, 16.0, 49, 3.9),
createData('姜饼', 400, 3.7, 67, 4.3),
];
export default function BasicTable() {
return (
<TableContainer component={Paper}>
<Table sx={
{ minWidth: 650 }} aria-label="简单表格">
<TableHead>
<TableRow>
<TableCell>甜点 (100g)</TableCell>
<TableCell align="right">卡路里</TableCell>
<TableCell align="right">脂肪 (g)</TableCell>
<TableCell align="right">碳水化合物 (g)</TableCell>
<TableCell align="right">蛋白质 (g)</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow
key={row.name}
sx={
{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="right">{row.calories}</TableCell>
<TableCell align="right">{row.fat}</TableCell>
<TableCell align="right">{row.carbs}</TableCell>
<TableCell align="right">{row.protein}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
3.5 反馈组件
对话框 Dialog
import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
export default function AlertDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<React.Fragment>
<Button variant="outlined" onClick={handleClickOpen}>
打开对话框
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
使用Google位置服务?
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
让Google帮助您的应用确定位置。这意味着将匿名的位置数据发送给Google,即使应用没有运行中。
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>拒绝</Button>
<Button onClick={handleClose} autoFocus>
同意
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
轻提示 Snackbar
import * as React from 'react';
import Button from '@mui/material/Button';
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
export default function CustomizedSnackbars() {
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(true);
};
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
return (
<div>
<Button onClick={handleClick}>打开提示</Button>
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity="success" sx={
{ width: '100%' }}>
操作成功!
</Alert>
</Snackbar>
</div>
);
}
四、主题定制
4.1 createTheme API
Material UI的主题定制通过createTheme函数实现。你可以覆盖调色板、字体、形状、间距等主题变量。
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
// 调色板配置
palette: {
mode: 'light', // 'light' 或 'dark'
primary: {
main: '#1976d2', // 主色
light: '#42a5f5',
dark: '#1565c0',
contrastText: '#fff',
},
secondary: {
main: '#dc004e',
light: '#ff4081',
dark: '#9a0036',
},
error: {
main: '#f44336',
},
warning: {
main: '#ff9800',
},
info: {
main: '#2196f3',
},
success: {
main: '#4caf50',
},
background: {
default: '#f5f5f5',
paper: '#ffffff',
},
text: {
primary: 'rgba(0, 0, 0, 0.87)',
secondary: 'rgba(0, 0, 0, 0.6)',
disabled: 'rgba(0, 0, 0, 0.38)',
},
},
// 字体配置
typography: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
fontSize: 14,
fontWeightLight: 300,
fontWeightRegular: 400,
fontWeightMedium: 500,
fontWeightBold: 700,
h1: {
fontSize: '6rem',
fontWeight: 300,
lineHeight: 1.167,
},
h2: {
fontSize: '3.75rem',
fontWeight: 300,
lineHeight: 1.2,
},
button: {
textTransform: 'none', // 按钮文字不自动大写
},
},
// 形状配置(圆角)
shape: {
borderRadius: 8,
},
// 间距配置(单位:px)
spacing: 8, // theme.spacing(1) = 8px
// 组件默认属性
components: {
MuiButton: {
defaultProps: {
disableRipple: true, // 禁用涟漪效果
},
styleOverrides: {
root: {
borderRadius: 8,
},
},
},
},
});
4.2 暗色模式
Material UI内置了对暗色模式的支持,只需将palette.mode设置为'dark'即可。
import * as React from 'react';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import Switch from '@mui/material/Switch';
import Button from '@mui/material/Button';
export default function DarkModeToggle() {
const [mode, setMode] = React.useState('light');
const darkTheme = createTheme({
palette: {
mode,
},
});
const toggleMode = () => {
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
};
return (
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<Switch checked={mode === 'dark'} onChange={toggleMode} />
<Button variant="contained">主题按钮</Button>
</ThemeProvider>
);
}
4.3 CSS主题变量(CSS Theme Variables)
Material UI v5.6.0引入了CSS主题变量支持(实验性功能),并在v7中进一步完善。这个特性使用CSS变量代替JavaScript运行时计算,带来了多方面的优势。
主要优势:
防止暗色模式SSR闪烁:CSS变量在服务端即可生成,客户端无需重新计算样式
支持无限颜色方案:可以创建除light和dark之外的更多颜色模式
更好的调试体验:开发者工具中可以直接看到CSS变量的值
跨标签页同步:颜色方案在不同浏览器标签页之间自动同步
简化第三方集成:CSS主题变量全局可用,集成更容易
启用CSS主题变量:
import { experimental_extendTheme as extendTheme } from '@mui/material/styles';
const theme = extendTheme({
colorSchemes: {
light: {
palette: {
primary: { main: '#1976d2' },
},
},
dark: {
palette: {
primary: { main: '#90caf9' },
},
},
},
});
在v7中,Material UI进一步改进了ESM支持,通过package.json中的exports字段明确支持有效的ESM和CommonJS,修复了Vite和Webpack等打包工具的多个问题。
4.4 样式覆盖的演进:从makeStyles到sx prop
Material UI的样式方案经历了重要演进:
v4时代:makeStyles + JSS
在v4中,样式覆盖主要通过makeStyles和withStyles实现,底层使用JSS(CSS-in-JS库)。
// v4写法(已过时)
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
root: {
backgroundColor: 'red',
},
}));
function MyComponent() {
const classes = useStyles();
return <div className={classes.root}>内容</div>;
}
v5+时代:sx prop + Emotion
v5+全面拥抱Emotion作为样式引擎,引入了sx prop,提供了更直观、更强大的样式定制方式。
// v5+推荐写法
<Button
sx={
{
bgcolor: 'primary.main',
'&:hover': {
bgcolor: 'primary.dark',
},
'@media (min-width: 600px)': {
width: 200,
},
}}
>
样式按钮
</Button>
styled API
Material UI也支持使用styled函数创建样式化组件:
import { styled } from '@mui/material/styles';
import Button from '@mui/material/Button';
const CustomButton = styled(Button)(({ theme }) => ({
backgroundColor: theme.palette.primary.main,
padding: theme.spacing(1, 2),
borderRadius: theme.shape.borderRadius,
'&:hover': {
backgroundColor: theme.palette.primary.dark,
},
}));