Material-UI 教程

项目概述

Material-UI(现在称为 MUI)是一套基于 Material Design 的 React 组件库,提供了丰富的预构建组件,帮助开发者快速构建现代化、美观的 Web 应用。Material-UI 遵循 Google 的 Material Design 设计规范,提供了一致的视觉语言和交互体验,同时保持了高度的可定制性。

主要特点

  • Material Design 实现:严格遵循 Google 的 Material Design 设计规范
  • 丰富的组件库:提供了大量常用的 UI 组件
  • 主题定制:支持深度定制主题,满足不同品牌需求
  • 响应式设计:适配各种屏幕尺寸
  • TypeScript 支持:完整的类型定义
  • 性能优化:组件经过性能优化,确保流畅的用户体验
  • 无障碍支持:符合 WCAG 标准,支持屏幕阅读器
  • 生态系统丰富:提供了多个相关库,如 MUI X、MUI System 等

适用场景

  • 构建现代化的 Web 应用
  • 需要遵循 Material Design 规范的项目
  • 快速原型开发
  • 企业级应用
  • 需要高度可定制 UI 的项目

安装与设置

方法一:使用 npm 或 yarn 安装

# 使用 npm
npm install @mui/material @emotion/react @emotion/styled

# 使用 yarn
yarn add @mui/material @emotion/react @emotion/styled

方法二:使用 CDN 引入

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Material-UI 示例</title>
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
  <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@mui/material@5.14.18/umd/material-ui.development.js"></script>
</head>
<body>
  <div id="root"></div>
  <script>
    const { Button } = MaterialUI;
    
    ReactDOM.render(
      <Button variant="contained">Hello Material-UI</Button>,
      document.getElementById('root')
    );
  </script>
</body>
</html>

方法三:使用 Create React App

# 创建项目
npx create-react-app my-app
cd my-app

# 安装 Material-UI
npm install @mui/material @emotion/react @emotion/styled

方法四:使用 Vite

# 创建项目
npm create vite@latest my-app -- --template react
cd my-app

# 安装 Material-UI
npm install @mui/material @emotion/react @emotion/styled

核心概念

1. 组件使用

Material-UI 提供了丰富的组件,使用方式非常简单:

import React from 'react';
import { Button, TextField, Checkbox } from '@mui/material';

function App() {
  return (
    <div>
      <Button variant="contained" color="primary">
        Primary Button
      </Button>
      <TextField label="Basic text field" variant="outlined" />
      <Checkbox label="Remember me" />
    </div>
  );
}

export default App;

2. 主题系统

Material-UI 提供了强大的主题系统,支持定制颜色、排版、间距等:

import React from 'react';
import { createTheme, ThemeProvider, Button } from '@mui/material';

// 创建自定义主题
const theme = createTheme({
  palette: {
    primary: {
      main: '#1976d2',
    },
    secondary: {
      main: '#dc004e',
    },
  },
  typography: {
    fontFamily: 'Roboto, Arial, sans-serif',
  },
});

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Button variant="contained" color="primary">
        Themed Button
      </Button>
      <Button variant="contained" color="secondary">
        Secondary Button
      </Button>
    </ThemeProvider>
  );
}

export default App;

3. 布局系统

Material-UI 提供了多种布局组件,如 Grid、Box、Stack 等:

import React from 'react';
import { Grid, Box, Stack, Paper } from '@mui/material';

function App() {
  return (
    <Box sx={{ flexGrow: 1, p: 3 }}>
      {/* 使用 Grid 布局 */}
      <Grid container spacing={3}>
        <Grid item xs={12} sm={6} md={4}>
          <Paper sx={{ p: 2 }}>Grid Item 1</Paper>
        </Grid>
        <Grid item xs={12} sm={6} md={4}>
          <Paper sx={{ p: 2 }}>Grid Item 2</Paper>
        </Grid>
        <Grid item xs={12} md={4}>
          <Paper sx={{ p: 2 }}>Grid Item 3</Paper>
        </Grid>
      </Grid>

      {/* 使用 Stack 布局 */}
      <Stack direction="row" spacing={2} sx={{ mt: 3 }}>
        <Paper sx={{ p: 2, flexGrow: 1 }}>Stack Item 1</Paper>
        <Paper sx={{ p: 2, flexGrow: 1 }}>Stack Item 2</Paper>
        <Paper sx={{ p: 2, flexGrow: 1 }}>Stack Item 3</Paper>
      </Stack>
    </Box>
  );
}

export default App;

4. 样式解决方案

Material-UI 提供了多种样式解决方案,包括:

使用 sx 属性

import React from 'react';
import { Box, Button } from '@mui/material';

function App() {
  return (
    <Box
      sx={{
        width: 300,
        height: 200,
        bgcolor: 'primary.main',
        color: 'white',
        p: 2,
        borderRadius: 2,
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      <Button
        sx={{
          bgcolor: 'white',
          color: 'primary.main',
          '&:hover': {
            bgcolor: 'grey.100',
          },
        }}
      >
        Styled Button
      </Button>
    </Box>
  );
}

export default App;

使用 styled 组件

import React from 'react';
import { styled, Button } from '@mui/material';

// 创建 styled 组件
const StyledBox = styled('div')({
  width: 300,
  height: 200,
  backgroundColor: 'primary.main',
  color: 'white',
  padding: 16,
  borderRadius: 8,
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
});

const StyledButton = styled(Button)({
  backgroundColor: 'white',
  color: 'primary.main',
  '&:hover': {
    backgroundColor: 'grey.100',
  },
});

function App() {
  return (
    <StyledBox>
      <StyledButton>Styled Button</StyledButton>
    </StyledBox>
  );
}

export default App;

5. 状态管理

Material-UI 组件通常通过 props 和 React 的状态管理进行控制:

import React, { useState } from 'react';
import { TextField, Button, List, ListItem, ListItemText } from '@mui/material';

function App() {
  const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
  const [newItem, setNewItem] = useState('');

  const handleAddItem = () => {
    if (newItem.trim()) {
      setItems([...items, newItem]);
      setNewItem('');
    }
  };

  return (
    <div>
      <TextField
        label="Add new item"
        variant="outlined"
        value={newItem}
        onChange={(e) => setNewItem(e.target.value)}
        style={{ marginRight: 8, width: 300 }}
      />
      <Button variant="contained" color="primary" onClick={handleAddItem}>
        Add Item
      </Button>
      <List style={{ marginTop: 16, width: 400 }}>
        {items.map((item, index) => (
          <ListItem key={index}>
            <ListItemText primary={item} />
          </ListItem>
        ))}
      </List>
    </div>
  );
}

export default App;

常用组件

1. Button

按钮组件用于触发操作:

import React from 'react';
import { Button, Stack } from '@mui/material';

function ButtonExample() {
  return (
    <Stack direction="row" spacing={2}>
      <Button variant="text">Text Button</Button>
      <Button variant="outlined">Outlined Button</Button>
      <Button variant="contained">Contained Button</Button>
      <Button variant="contained" color="secondary">
        Secondary Button
      </Button>
      <Button variant="contained" disabled>
        Disabled Button
      </Button>
    </Stack>
  );
}

export default ButtonExample;

2. TextField

文本输入框组件用于收集用户输入:

import React, { useState } from 'react';
import { TextField, Stack } from '@mui/material';

function TextFieldExample() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  return (
    <Stack spacing={2} width={300}>
      <TextField
        label="Name"
        variant="outlined"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <TextField
        label="Email"
        variant="outlined"
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <TextField
        label="Password"
        variant="outlined"
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
    </Stack>
  );
}

export default TextFieldExample;

3. Card

卡片组件用于展示相关信息的集合:

import React from 'react';
import { Card, CardContent, CardMedia, Typography, Button, CardActions } from '@mui/material';

function CardExample() {
  return (
    <Card sx={{ maxWidth: 345 }}>
      <CardMedia
        component="img"
        height="140"
        image="https://example.com/image.jpg"
        alt="Card image"
      />
      <CardContent>
        <Typography gutterBottom variant="h5" component="div">
          Card Title
        </Typography>
        <Typography variant="body2" color="text.secondary">
          This is a sample card. Card content goes here. You can add any text or components inside the card.
        </Typography>
      </CardContent>
      <CardActions>
        <Button size="small">Share</Button>
        <Button size="small">Learn More</Button>
      </CardActions>
    </Card>
  );
}

export default CardExample;

4. Dialog

对话框组件用于显示重要信息或请求用户确认:

import React, { useState } from 'react';
import { Button, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, TextField } from '@mui/material';

function DialogExample() {
  const [open, setOpen] = useState(false);
  const [name, setName] = useState('');

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  const handleSave = () => {
    console.log('Saved name:', name);
    setOpen(false);
  };

  return (
    <div>
      <Button variant="contained" onClick={handleClickOpen}>
        Open Dialog
      </Button>
      <Dialog open={open} onClose={handleClose}>
        <DialogTitle>Dialog Title</DialogTitle>
        <DialogContent>
          <DialogContentText>
            Please enter your name:
          </DialogContentText>
          <TextField
            autoFocus
            margin="dense"
            label="Name"
            type="text"
            fullWidth
            variant="outlined"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
        </DialogContent>
        <DialogActions>
          <Button onClick={handleClose}>Cancel</Button>
          <Button onClick={handleSave}>Save</Button>
        </DialogActions>
      </Dialog>
    </div>
  );
}

export default DialogExample;

5. AppBar

应用栏组件用于显示应用的标题、导航和操作:

import React from 'react';
import { AppBar, Toolbar, Typography, Button, IconButton, Menu, MenuItem } from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';

function AppBarExample() {
  const [anchorEl, setAnchorEl] = React.useState(null);
  const open = Boolean(anchorEl);

  const handleMenu = (event) => {
    setAnchorEl(event.currentTarget);
  };

  const handleClose = () => {
    setAnchorEl(null);
  };

  return (
    <AppBar position="static">
      <Toolbar>
        <IconButton
          size="large"
          edge="start"
          color="inherit"
          aria-label="menu"
          sx={{ mr: 2 }}
          onClick={handleMenu}
        >
          <MenuIcon />
        </IconButton>
        <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
          App Title
        </Typography>
        <Button color="inherit">Login</Button>
        <Menu
          id="menu-appbar"
          anchorEl={anchorEl}
          anchorOrigin={{
            vertical: 'top',
            horizontal: 'left',
          }}
          keepMounted
          transformOrigin={{
            vertical: 'top',
            horizontal: 'left',
          }}
          open={open}
          onClose={handleClose}
        >
          <MenuItem onClick={handleClose}>Profile</MenuItem>
          <MenuItem onClick={handleClose}>My account</MenuItem>
          <MenuItem onClick={handleClose}>Logout</MenuItem>
        </Menu>
      </Toolbar>
    </AppBar>
  );
}

export default AppBarExample;

6. Card

卡片组件用于展示相关信息的集合:

import React from 'react';
import { Card, CardContent, CardMedia, Typography, Button, CardActions } from '@mui/material';

function CardExample() {
  return (
    <Card sx={{ maxWidth: 345 }}>
      <CardMedia
        component="img"
        height="140"
        image="https://example.com/image.jpg"
        alt="Card image"
      />
      <CardContent>
        <Typography gutterBottom variant="h5" component="div">
          Card Title
        </Typography>
        <Typography variant="body2" color="text.secondary">
          This is a sample card. Card content goes here. You can add any text or components inside the card.
        </Typography>
      </CardContent>
      <CardActions>
        <Button size="small">Share</Button>
        <Button size="small">Learn More</Button>
      </CardActions>
    </Card>
  );
}

export default CardExample;

7. Tabs

标签页组件用于在同一页面中切换不同的内容:

import React, { useState } from 'react';
import { Tabs, Tab, Box, Typography } from '@mui/material';

function TabsExample() {
  const [value, setValue] = useState(0);

  const handleChange = (event, newValue) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ width: '100%', maxWidth: 500 }}>
      <Tabs
        value={value}
        onChange={handleChange}
        variant="fullWidth"
        centered
      >
        <Tab label="Tab 1" />
        <Tab label="Tab 2" />
        <Tab label="Tab 3" />
      </Tabs>
      <Box sx={{ p: 3 }}>
        {value === 0 && <Typography>Content of Tab 1</Typography>}
        {value === 1 && <Typography>Content of Tab 2</Typography>}
        {value === 2 && <Typography>Content of Tab 3</Typography>}
      </Box>
    </Box>
  );
}

export default TabsExample;

8. Select

选择器组件用于从选项中选择值:

import React, { useState } from 'react';
import { FormControl, InputLabel, Select, MenuItem, Box } from '@mui/material';

function SelectExample() {
  const [age, setAge] = useState('');

  const handleChange = (event) => {
    setAge(event.target.value);
  };

  return (
    <Box sx={{ minWidth: 120 }}>
      <FormControl fullWidth>
        <InputLabel id="age-select-label">Age</InputLabel>
        <Select
          labelId="age-select-label"
          id="age-select"
          value={age}
          label="Age"
          onChange={handleChange}
        >
          <MenuItem value={10}>Ten</MenuItem>
          <MenuItem value={20}>Twenty</MenuItem>
          <MenuItem value={30}>Thirty</MenuItem>
        </Select>
      </FormControl>
    </Box>
  );
}

export default SelectExample;

高级功能

1. 自定义主题

Material-UI 支持深度定制主题,包括颜色、排版、间距等:

import React from 'react';
import { createTheme, ThemeProvider, Button, Typography, Box } from '@mui/material';

// 创建自定义主题
const theme = createTheme({
  palette: {
    primary: {
      main: '#2196f3',
      light: '#64b5f6',
      dark: '#1976d2',
      contrastText: '#fff',
    },
    secondary: {
      main: '#f50057',
      light: '#ff4081',
      dark: '#c51162',
      contrastText: '#fff',
    },
    background: {
      default: '#f5f5f5',
      paper: '#fff',
    },
  },
  typography: {
    fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
    h1: {
      fontSize: '2.5rem',
      fontWeight: 500,
    },
    h2: {
      fontSize: '2rem',
      fontWeight: 500,
    },
  },
  spacing: 8,
  shape: {
    borderRadius: 8,
  },
});

function CustomThemeExample() {
  return (
    <ThemeProvider theme={theme}>
      <Box sx={{ p: 3, bgcolor: 'background.default', minHeight: 300 }}>
        <Typography variant="h1" component="h2" gutterBottom>
          Custom Theme Example
        </Typography>
        <Button variant="contained" color="primary" sx={{ mr: 2 }}>
          Primary Button
        </Button>
        <Button variant="contained" color="secondary">
          Secondary Button
        </Button>
      </Box>
    </ThemeProvider>
  );
}

export default CustomThemeExample;

2. 样式系统

Material-UI 提供了强大的样式系统,包括 sx 属性、styled 组件和系统实用程序:

import React from 'react';
import { styled, Box, Button } from '@mui/material';

// 使用 styled 组件
const StyledContainer = styled(Box)(({ theme }) => ({
  display: 'flex',
  flexDirection: 'column',
  alignItems: 'center',
  justifyContent: 'center',
  padding: theme.spacing(4),
  backgroundColor: theme.palette.grey[100],
  borderRadius: theme.shape.borderRadius,
  boxShadow: theme.shadows[2],
  [theme.breakpoints.up('md')]: {
    flexDirection: 'row',
    gap: theme.spacing(2),
  },
}));

// 使用 sx 属性
function StyleSystemExample() {
  return (
    <StyledContainer>
      <Button
        variant="contained"
        sx={{
          bgcolor: 'primary.main',
          '&:hover': {
            bgcolor: 'primary.dark',
          },
          px: 4,
          py: 1.5,
          borderRadius: 2,
        }}
      >
        Styled Button 1
      </Button>
      <Button
        variant="outlined"
        sx={{
          borderColor: 'secondary.main',
          color: 'secondary.main',
          '&:hover': {
            borderColor: 'secondary.dark',
            bgcolor: 'secondary.light',
          },
          px: 4,
          py: 1.5,
          borderRadius: 2,
        }}
      >
        Styled Button 2
      </Button>
    </StyledContainer>
  );
}

export default StyleSystemExample;

3. 性能优化

对于大型应用,可以使用一些技巧来优化 Material-UI 组件的性能:

使用 React.memo

import React, { memo } from 'react';
import { List, ListItem, ListItemText } from '@mui/material';

const DataList = memo(({ items }) => {
  return (
    <List>
      {items.map((item, index) => (
        <ListItem key={index}>
          <ListItemText primary={item} />
        </ListItem>
      ))}
    </List>
  );
});

function App() {
  const [items] = React.useState(['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5']);
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Increment: {count}
      </button>
      <DataList items={items} />
    </div>
  );
}

export default App;

使用 useMemo 和 useCallback

import React, { useMemo, useCallback, useState } from 'react';
import { TextField, Button, List, ListItem, ListItemText } from '@mui/material';

function App() {
  const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
  const [newItem, setNewItem] = useState('');
  const [filter, setFilter] = useState('');

  // 使用 useMemo 缓存过滤后的列表
  const filteredItems = useMemo(() => {
    if (!filter) return items;
    return items.filter(item => item.toLowerCase().includes(filter.toLowerCase()));
  }, [items, filter]);

  // 使用 useCallback 缓存事件处理函数
  const handleAddItem = useCallback(() => {
    if (newItem.trim()) {
      setItems(prevItems => [...prevItems, newItem]);
      setNewItem('');
    }
  }, [newItem]);

  return (
    <div>
      <TextField
        label="Filter"
        variant="outlined"
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        style={{ marginBottom: 16, width: 300 }}
      />
      <TextField
        label="Add new item"
        variant="outlined"
        value={newItem}
        onChange={(e) => setNewItem(e.target.value)}
        style={{ marginRight: 8, width: 300 }}
      />
      <Button variant="contained" color="primary" onClick={handleAddItem}>
        Add Item
      </Button>
      <List style={{ marginTop: 16, width: 400 }}>
        {filteredItems.map((item, index) => (
          <ListItem key={index}>
            <ListItemText primary={item} />
          </ListItem>
        ))}
      </List>
    </div>
  );
}

export default App;

4. 无障碍支持

Material-UI 提供了内置的无障碍支持,同时也提供了一些工具来帮助开发者创建更无障碍的应用:

import React from 'react';
import { Button, TextField, FormControlLabel, Checkbox, Box } from '@mui/material';
import { useId } from '@mui/material/utils';

function AccessibilityExample() {
  // 使用 useId 创建唯一的 ID,确保无障碍性
  const nameId = useId();
  const emailId = useId();
  const termsId = useId();

  return (
    <Box sx={{ p: 3, maxWidth: 400 }}>
      <TextField
        id={nameId}
        label="Name"
        variant="outlined"
        fullWidth
        margin="normal"
        aria-required="true"
      />
      <TextField
        id={emailId}
        label="Email"
        variant="outlined"
        type="email"
        fullWidth
        margin="normal"
        aria-required="true"
      />
      <FormControlLabel
        control={<Checkbox id={termsId} />}
        label="I agree to the terms and conditions"
      />
      <Button
        variant="contained"
        color="primary"
        fullWidth
        sx={{ mt: 2 }}
      >
        Submit
      </Button>
    </Box>
  );
}

export default AccessibilityExample;

最佳实践

1. 组件组织

  • 按功能模块组织组件
  • 使用文件夹结构管理相关组件
  • 为组件添加清晰的文档和注释

2. 样式管理

  • 使用 Material-UI 的样式系统(sx 属性、styled 组件)
  • 避免直接覆盖组件样式
  • 对于全局样式,使用 createTheme 进行配置

3. 性能优化

  • 使用 React.memo 缓存组件
  • 合理使用 useMemo 和 useCallback
  • 避免在 render 函数中创建不必要的对象
  • 对于长列表,使用虚拟滚动

4. 可访问性

  • 使用语义化的 HTML 元素
  • 确保所有交互元素都有适当的 ARIA 属性
  • 测试组件在屏幕阅读器中的表现
  • 确保键盘导航正常工作

5. 主题管理

  • 为整个应用使用单一的主题提供者
  • 集中管理主题配置
  • 对于需要不同主题的部分,使用嵌套的 ThemeProvider

6. 测试

  • 为组件编写单元测试
  • 测试组件在不同主题下的表现
  • 测试组件的响应式行为
  • 测试表单验证逻辑

常见问题与解决方案

1. 样式冲突

问题:Material-UI 样式与项目其他样式冲突。

解决方案

  • 使用 CSS Modules 或 styled-components
  • 为 Material-UI 组件添加前缀
  • 使用 createTheme 的 components 属性覆盖组件样式

2. 组件渲染性能问题

问题:大型表单或表格渲染缓慢。

解决方案

  • 使用虚拟滚动
  • 优化组件渲染,避免不必要的重新渲染
  • 使用 React.lazy 和 Suspense 懒加载组件

3. 主题定制问题

问题:主题定制不生效。

解决方案

  • 确保正确配置 createTheme
  • 检查 ThemeProvider 的嵌套顺序
  • 验证组件是否支持主题定制

4. 响应式设计问题

问题:组件在某些屏幕尺寸下显示异常。

解决方案

  • 使用 Material-UI 的断点系统
  • 为不同屏幕尺寸设计不同的布局
  • 测试组件在各种屏幕尺寸下的表现

5. 国际化配置问题

问题:国际化配置不生效。

解决方案

  • 使用 Material-UI 的 LocalizationProvider
  • 确保正确导入语言包
  • 验证组件是否支持国际化

参考资源

总结

Material-UI 是一套功能强大、设计精美的 React 组件库,基于 Google 的 Material Design 设计规范,提供了丰富的预构建组件,帮助开发者快速构建现代化的 Web 应用。通过本教程的学习,你应该已经掌握了 Material-UI 的核心概念、使用方法和最佳实践。

Material-UI 的生态系统非常丰富,包括 MUI Core(核心组件库)、MUI X(高级组件,如数据网格、日期选择器等)、MUI System(样式系统)、MUI Base(无样式组件)等。这些工具可以帮助你更高效地构建各种类型的应用。

作为最流行的 React UI 组件库之一,Material-UI 拥有庞大的开发者社区和完善的文档,这使得它成为构建现代化 Web 应用的理想选择。无论是简单的原型还是复杂的企业级应用,Material-UI 都能满足你的需求。

随着 Material-UI 的不断发展,它也在持续改进和更新,添加新的组件和功能,以适应现代 Web 开发的需求。建议你持续关注 Material-UI 的官方文档和社区动态,以便及时了解最新的功能和最佳实践。

« 上一篇 Ant Design 教程 - 企业级 UI 设计语言和 React 组件库 下一篇 » Next.js 教程 - 基于 React 的全栈框架