grid布局可以用于两个方向上的或者响应式的布局。于此相对的是Stack,用于一个方向上的布局,水平或垂直。

xxxxxxxxxx371// mycode\react-mui-demo\src\components\MuiLayout.tsx23import { Box, Stack, Divider, Grid } from "@mui/material";45export const MuiLayout = () => {6 return (7 <>8 9 10 {/* Grid上面的container属性,表示创建一个Grid容器,因为Grid默认为一个Grid Item. */}11 <Grid container my={4}>12 {/* Grid container默认分为12列,可以通过指定它的size属性来设置Grid Item占几列。 <Grid size={4}> 指定的是所有屏幕条件下的固定值 */}13 {/* 如果想区分不同的屏幕条件下,占用不同的宽度,可以通过breakpoints来表示,xs、sm、md、lg、xl等等 */}14 <Grid size={{ xs: 12, sm: 6 }}>15 <Box bgcolor="primary.light" p={2}>16 Item 117 </Box>18 </Grid>19 <Grid size={{ xs: 12, sm: 6 }}>20 <Box bgcolor="primary.light" p={2}>21 Item 222 </Box>23 </Grid>24 <Grid size={{ xs: 12, sm: 6 }}>25 <Box bgcolor="primary.light" p={2}>26 Item 327 </Box>28 </Grid>29 <Grid size={{ xs: 12, sm: 6 }}>30 <Box bgcolor="primary.light" p={2}>31 Item 432 </Box>33 </Grid>34 </Grid>35 </>36 );37};可以看到,设置了不同的breakpoints情况之后,在不同的屏幕条件下,会展示不同的占用宽度。

如果不指定size属性,Grid Item默认是平均分布。如果有某一项、或多项指定了size属性,那其余的Grid Item就会平分其余的宽度。
size="auto"表示会根据GridItem里面内容的宽度来设置宽度。
<Grid container>上面的spacing属性,用来设置Grid Item之间横向、纵向的间距。

Paper组件主要用于模拟现实世界中一张纸的质感和效果,可以用于创建有层次感的区域,可以用于创建Card、Modal等等的外观基础。
xxxxxxxxxx121// mycode\react-mui-demo\src\components\MuiLayout.tsx23import { Box, Stack, Divider, Grid, Paper } from "@mui/material";45export const MuiLayout = () => {6 return (7 // elevation属性用于设置阴影效果8 <Paper sx={{ padding: "20px" }} elevation={4}>9 ......10 </Paper>11 );12};

xxxxxxxxxx241// mycode\react-mui-demo\src\components\MuiCard.tsx23import { Card, CardContent, Box, Typography } from "@mui/material";45export const MuiCard = () => {6 return (7 <Box width="300px">8 <Card>9 <CardContent>10 <Typography gutterBottom variant="h5">11 React12 </Typography>13 <Typography variant="body2" color="text.secondary">14 Lorem ipsum dolor sit amet consectetur adipisicing elit. Nesciunt15 error inventore, explicabo molestias minus excepturi eligendi illo16 sit possimus voluptatum quisquam repudiandae nostrum id17 necessitatibus odio, saepe est nihil molestiae?18 </Typography>19 </CardContent>20 </Card>21 </Box>22 );23};24
CardActions添加按钮xxxxxxxxxx321import {2 Card,3 CardContent,4 Box,5 Typography,6 CardActions,7 Button,8} from "@mui/material";910export const MuiCard = () => {11 return (12 <Box width="300px">13 <Card>14 <CardContent>15 <Typography gutterBottom variant="h5">16 React17 </Typography>18 <Typography variant="body2" color="text.secondary">19 Lorem ipsum dolor sit amet consectetur adipisicing elit. Nesciunt20 error inventore, explicabo molestias minus excepturi eligendi illo21 sit possimus voluptatum quisquam repudiandae nostrum id22 necessitatibus odio, saepe est nihil molestiae?23 </Typography>24 </CardContent>25 <CardActions>26 <Button size="small">Share</Button>27 <Button size="small">Learn more</Button>28 </CardActions>29 </Card>30 </Box>31 );32};
CardMedia添加图片xxxxxxxxxx411// 23import {4 Card,5 CardContent,6 Box,7 Typography,8 CardActions,9 Button,10 CardMedia,11} from "@mui/material";1213export const MuiCard = () => {14 return (15 <Box width="300px">16 <Card>17 <CardMedia18 component="img"19 height="140"20 image="https://picsum.photos/200/300"21 alt="upsplash image"22 />23 <CardContent>24 <Typography gutterBottom variant="h5">25 React26 </Typography>27 <Typography variant="body2" color="text.secondary">28 Lorem ipsum dolor sit amet consectetur adipisicing elit. Nesciunt29 error inventore, explicabo molestias minus excepturi eligendi illo30 sit possimus voluptatum quisquam repudiandae nostrum id31 necessitatibus odio, saepe est nihil molestiae?32 </Typography>33 </CardContent>34 <CardActions>35 <Button size="small">Share</Button>36 <Button size="small">Learn more</Button>37 </CardActions>38 </Card>39 </Box>40 );41};
Accordion:手风琴。
xxxxxxxxxx331// mycode\react-mui-demo\src\components\MuiAccordion.tsx23import {4 Accordion,5 AccordionSummary,6 AccordionDetails,7 Typography,8} from "@mui/material";9import ExpandMoreIcon from "@mui/icons-material/ExpandMore";1011export const MuiAccordion = () => {12 return (13 <>14 <Accordion>15 <AccordionSummary16 id="panel1-header"17 aria-controls="panel1-content"18 expandIcon={<ExpandMoreIcon />}19 >20 <Typography>Accordion 1</Typography>21 </AccordionSummary>22 <AccordionDetails>23 <Typography>24 Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis25 quaerat culpa ex eius placeat aspernatur ea, amet expedita ullam26 eveniet dolores ut iure quisquam est architecto consequatur animi,27 non earum!28 </Typography>29 </AccordionDetails>30 </Accordion>31 </>32 );33};
直接复制之前的Accordion,就可以组成group了,最外层不需要加额外的标签。
xxxxxxxxxx651import {2 Accordion,3 AccordionSummary,4 AccordionDetails,5 Typography,6} from "@mui/material";7import ExpandMoreIcon from "@mui/icons-material/ExpandMore";89export const MuiAccordion = () => {10 return (11 <>12 <Accordion>13 <AccordionSummary14 id="panel1-header"15 aria-controls="panel1-content"16 expandIcon={<ExpandMoreIcon />}17 >18 <Typography>Accordion 1</Typography>19 </AccordionSummary>20 <AccordionDetails>21 <Typography>22 Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis23 quaerat culpa ex eius placeat aspernatur ea, amet expedita ullam24 eveniet dolores ut iure quisquam est architecto consequatur animi,25 non earum!26 </Typography>27 </AccordionDetails>28 </Accordion>29 <Accordion>30 <AccordionSummary31 id="panel2-header"32 aria-controls="panel2-content"33 expandIcon={<ExpandMoreIcon />}34 >35 <Typography>Accordion 2</Typography>36 </AccordionSummary>37 <AccordionDetails>38 <Typography>39 Lorem ipsum, dolor sit amet consectetur adipisicing elit. In40 consequuntur praesentium aut quasi dolorem ut, repellendus error41 dolor omnis fugiat corporis temporibus dolorum illum, a explicabo42 voluptates odit architecto inventore?43 </Typography>44 </AccordionDetails>45 </Accordion>46 <Accordion>47 <AccordionSummary48 id="panel3-header"49 aria-controls="panel3-content"50 expandIcon={<ExpandMoreIcon />}51 >52 <Typography>Accordion 3</Typography>53 </AccordionSummary>54 <AccordionDetails>55 <Typography>56 Lorem ipsum dolor sit amet consectetur adipisicing elit. Quod quis57 praesentium, repudiandae, animi dolor soluta eaque sed perferendis58 perspiciatis expedita quo enim maxime atque vero assumenda ut fugit59 molestias ad!60 </Typography>61 </AccordionDetails>62 </Accordion>63 </>64 );65};
通过Accordion的expanded属性来处理。
xxxxxxxxxx821import {2 Accordion,3 AccordionSummary,4 AccordionDetails,5 Typography,6} from "@mui/material";7import ExpandMoreIcon from "@mui/icons-material/ExpandMore";8import { useState } from "react";910export const MuiAccordion = () => {11 const [isExpanded, setIsExpanded] = useState<string | boolean>(false);1213 const handleChange = (isExpanded: boolean, type: string) => {14 // 这一步其实是最难理解的,其实这个设置值有两个意思,第一个意思就是如果isExpanded为true,就设置isExpanded为某个string,这样Accordion里面的判断条件就会同步计算;第二个意思是如果点击了已经展开的Accordion,那么此时的isExpanded就为false,那么就设置isExpanded为false,此时所有Accordion里面的判断条件都不成立,都会关闭15 // 这就是通过onChange事件的值,来改变expanded的值,这一点要理解16 setIsExpanded(isExpanded ? type : false);17 };18 return (19 <>20 <Accordion21 expanded={isExpanded === "panel1"}22 onChange={(_e, isExpanded) => handleChange(isExpanded, "panel1")}23 >24 <AccordionSummary25 id="panel1-header"26 aria-controls="panel1-content"27 expandIcon={<ExpandMoreIcon />}28 >29 <Typography>Accordion 1</Typography>30 </AccordionSummary>31 <AccordionDetails>32 <Typography>33 Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis34 quaerat culpa ex eius placeat aspernatur ea, amet expedita ullam35 eveniet dolores ut iure quisquam est architecto consequatur animi,36 non earum!37 </Typography>38 </AccordionDetails>39 </Accordion>40 <Accordion41 expanded={isExpanded === "panel2"}42 onChange={(_e, isExpanded) => handleChange(isExpanded, "panel2")}43 >44 <AccordionSummary45 id="panel2-header"46 aria-controls="panel2-content"47 expandIcon={<ExpandMoreIcon />}48 >49 <Typography>Accordion 2</Typography>50 </AccordionSummary>51 <AccordionDetails>52 <Typography>53 Lorem ipsum, dolor sit amet consectetur adipisicing elit. In54 consequuntur praesentium aut quasi dolorem ut, repellendus error55 dolor omnis fugiat corporis temporibus dolorum illum, a explicabo56 voluptates odit architecto inventore?57 </Typography>58 </AccordionDetails>59 </Accordion>60 <Accordion61 expanded={isExpanded === "panel3"}62 onChange={(_e, isExpanded) => handleChange(isExpanded, "panel3")}63 >64 <AccordionSummary65 id="panel3-header"66 aria-controls="panel3-content"67 expandIcon={<ExpandMoreIcon />}68 >69 <Typography>Accordion 3</Typography>70 </AccordionSummary>71 <AccordionDetails>72 <Typography>73 Lorem ipsum dolor sit amet consectetur adipisicing elit. Quod quis74 praesentium, repudiandae, animi dolor soluta eaque sed perferendis75 perspiciatis expedita quo enim maxime atque vero assumenda ut fugit76 molestias ad!77 </Typography>78 </AccordionDetails>79 </Accordion>80 </>81 );82};上面这段代码的意思要弄清楚,这个设计很好。

imagelist的效果应该是照片墙的效果。
xxxxxxxxxx211// mycode\react-mui-demo\src\components\MuiImageList.tsx23import { Stack, ImageList, ImageListItem } from "@mui/material";45export const MuiImageList = () => {6 return (7 <Stack spacing={4}>8 <ImageList sx={{ width: 500, height: 450 }} cols={3} rowHeight={164}>9 {itemData.map((item) => (10 <ImageListItem key={item.img}>11 <img12 src={`${item.img}?w=164&h=164&fit=crop&auto=format&dpr=2`}13 alt={item.title}14 loading="lazy"15 />16 </ImageListItem>17 ))}18 </ImageList>19 </Stack>20 );21};仔细看一下network里面,调整到3G,当滚动到最下面的时候,图片才开始加载,这对于网速慢的很友好。

Woven image lists use alternating container ratios to create a rhythmic layout. A woven image list is best for browsing peer content.
交织式图片列表使用交替的容器比例来创建富有韵律感的布局。交织式图片列表最适合浏览同行内容。
xxxxxxxxxx171{/* 将variant属性设置为woven,然后不设置rowHeight,设置gap属性 */}2<ImageList3 sx={{ width: 500, height: 450 }}4 variant="woven"5 cols={3}6 gap={8}7 >8 {itemData2.map((item) => (9 <ImageListItem key={item.img}>10 <img11 src={`${item.img}?w=164&h=164&fit=crop&auto=format&dpr=2`}12 alt={item.title}13 loading="lazy"14 />15 </ImageListItem>16 ))}17</ImageList>效果:

Masonry image lists use dynamically sized container heights that reflect the aspect ratio of each image. This image list is best used for browsing uncropped peer content.
瀑布流图像列表使用动态调整的容器高度,以反映每张图片的宽高比。这种图像列表最适合浏览未经裁剪的同行内容。
xxxxxxxxxx141<Box sx={{ width: 500, height: 450, overflowY: "scroll" }}>2 {/* 将variant属性设置为masonry,然后不设置rowHeight,设置gap属性 */}3 <ImageList variant="masonry" cols={3} gap={8}>4 {itemData3.map((item) => (5 <ImageListItem key={item.img}>6 <img7 src={`${item.img}?w=248&fit=crop&auto=format&dpr=2`}8 alt={item.title}9 loading="lazy"10 />11 </ImageListItem>12 ))}13 </ImageList>14</Box>效果:

ImageListItemBar的作用是 add an overlay to each item。
xxxxxxxxxx271import {2 Stack,3 ImageList,4 ImageListItem,5 Box,6 ImageListItemBar,7} from "@mui/material";89export const MuiImageList = () => {10 return (11 <Stack spacing={4}>12 <ImageList sx={{ width: 500, height: 450 }} cols={3} rowHeight={164}>13 {itemData.map((item) => (14 <ImageListItem key={item.img}>15 <img16 src={`${item.img}?w=164&h=164&fit=crop&auto=format&dpr=2`}17 alt={item.title}18 loading="lazy"19 />20 {/* 在ImageListItem里面使用这个组件 */}21 <ImageListItemBar title={item.title} />22 </ImageListItem>23 ))}24 </ImageList>25 </Stack>26 );27};可以看到,非常漂亮的图片标题就展示了。

在mui中没有专门的navbar。但是可以通过两个组件AppBar和Toolbar来组合成navbar的样式。

基本用法:
xxxxxxxxxx191// mycode\react-mui-demo\src\components\MuiNavbar.tsx23import { AppBar, Toolbar, Typography, IconButton } from "@mui/material";4import CatchingPokemonIcon from "@mui/icons-material/CatchingPokemon";56export const MuiNavbar = () => {7 return (8 <AppBar position="static">9 <Toolbar>10 <IconButton size="large" edge="start" color="inherit" aria-label="logo">11 <CatchingPokemonIcon />12 </IconButton>13 <Typography variant="h6" component="div">14 Pokemon App15 </Typography>16 </Toolbar>17 </AppBar>18 );19};为了更好的展示navbar,要把App.css里面的body样式和#root样式的padding和margin全部设置为0。

为导航栏添加按钮,需要将某一部分设置为flexGrow: 1,意味着其余部分都是按照内容大小进行展示的。
xxxxxxxxxx311import {2 AppBar,3 Toolbar,4 Typography,5 IconButton,6 Stack,7 Button,8} from "@mui/material";9import CatchingPokemonIcon from "@mui/icons-material/CatchingPokemon";1011export const MuiNavbar = () => {12 return (13 <AppBar position="static">14 <Toolbar>15 <IconButton size="large" edge="start" color="inherit" aria-label="logo">16 <CatchingPokemonIcon />17 </IconButton>18 {/* 需要将某一部分变为flexGrow */}19 <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>20 Pokemon App21 </Typography>22 <Stack spacing={2} direction="row">23 <Button color="inherit">Features</Button>24 <Button color="inherit">Pricing</Button>25 <Button color="inherit">About</Button>26 <Button color="inherit">Login</Button>27 </Stack>28 </Toolbar>29 </AppBar>30 );31};
使用Menu和MenuItem来组合成Menu。
但是Menu的使用非常复杂,要定义很多变量和函数,还要相互绑定很多值,我问一下:
我们来详细看一下 MUI
Menu组件中最重要的两个部分:定位(anchorEl)和 状态管理(open/onClose)。
1. ⚓ 定位:
anchorEl(锚点元素)
anchorEl属性告诉Menu组件它应该“挂”在哪里。
- 定义:
anchorEl属性需要传入一个 DOM 元素的引用。这个 DOM 元素就是菜单的“锚点”,通常是触发菜单的那个按钮。- 用途:MUI 内部会使用这个 DOM 元素的坐标和大小来精确计算菜单的起始位置,确保菜单紧挨着触发元素弹出。
🔹 示例代码 (如何获取锚点):
通常,您会在按钮的点击事件中获取这个 DOM 元素的引用,并将其存储在 state 中:
xxxxxxxxxx441import * as React from 'react';2import Button from '@mui/material/Button';3import Menu from '@mui/material/Menu';4import MenuItem from '@mui/material/MenuItem';56function MyMenuComponent() {7// 1. 定义一个 state 来存储锚点元素(DOM 引用)8const [anchorEl, setAnchorEl] = React.useState(null);9const open = Boolean(anchorEl); // 菜单是否打开的状态1011// 2. 处理点击事件:设置锚点12const handleClick = (event) => {13// event.currentTarget 就是被点击的 Button 元素的 DOM 引用14setAnchorEl(event.currentTarget);15};1617// 3. 处理关闭事件:清除锚点18const handleClose = () => {19setAnchorEl(null);20};2122return (23<div>24<Button25id="basic-button"26aria-controls={open ? 'basic-menu' : undefined}27aria-haspopup="true"28aria-expanded={open ? 'true' : undefined}29onClick={handleClick} // <-- 点击时设置 anchorEl30>31打开菜单32</Button>33<Menu34id="basic-menu"35anchorEl={anchorEl} // <-- 将锚点传递给 Menu36open={open} // <-- 决定 Menu 是否显示37onClose={handleClose} // <-- 关闭时调用 handleClose38>39<MenuItem onClick={handleClose}>个人中心</MenuItem>40<MenuItem onClick={handleClose}>退出登录</MenuItem>41</Menu>42</div>43);44}
2. 🔄 状态管理:
open和onClose
Menu是一个受控组件(Controlled Component),这意味着您必须通过外部 State 来控制它的显示和隐藏。
open属性:
- 接收一个布尔值 (
true或false)。- 当为
true时,菜单显示;当为false时,菜单隐藏。- 在上面的例子中,我们使用
const open = Boolean(anchorEl);,巧妙地将是否有锚点(anchorEl是否为null)作为菜单是否打开的依据。
onClose属性:
接收一个回调函数 (
handleClose)。这个回调函数会在用户希望关闭菜单时被调用,例如:
- 用户点击了菜单项。
- 用户点击了菜单外部的区域 (Click-away)。
- 用户按下了
Esc键。您的
onClose函数(即handleClose)唯一的职责就是将您的外部状态(如anchorEl)重置,从而将open状态设为false,让菜单隐藏。通过这种
anchorEl+open/onClose的模式,MUI 将复杂的定位逻辑封装在内部,同时将显示和隐藏的控制权交给了开发者,这既灵活又符合 React 的最佳实践。
menu的功能实现,搞懂上面的原理之后就不难了,就是里面为了兼容无障碍,弄在一起就很复杂了。
案例:新增一个resources按钮,在点击这个按钮的时候,出现menu。
下面是纯粹功能的实现,没有报错:
xxxxxxxxxx711// mycode\react-mui-demo\src\components\MuiNavbar.tsx23import {4 AppBar,5 Toolbar,6 Typography,7 IconButton,8 Stack,9 Button,10 Menu,11 MenuItem,12} from "@mui/material";13import CatchingPokemonIcon from "@mui/icons-material/CatchingPokemon";14import { useState } from "react";15import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";1617export const MuiNavbar = () => {18 // 定义一个 state 来存储锚点元素(DOM引用)19 const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);2021 // 菜单是否打开的状态。为什么这个判断能成立呢?因为anchorEl状态变了之后,会引发函数的rerender,这句代码就会重新执行。22 const open = Boolean(anchorEl);2324 // 处理点击事件,设置锚点25 const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {26 setAnchorEl(e.currentTarget);27 };2829 // 处理关闭事件,清除锚点30 const handleClose = () => {31 setAnchorEl(null);32 };33 34 return (35 <AppBar position="static">36 <Toolbar>37 <IconButton size="large" edge="start" color="inherit" aria-label="logo">38 <CatchingPokemonIcon />39 </IconButton>40 <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>41 Pokemon App42 </Typography>43 <Stack spacing={2} direction="row">44 <Button color="inherit">Features</Button>45 <Button color="inherit">Pricing</Button>46 {/* 新增按钮,设置点击事件,设置尾部的图标 */}47 <Button48 color="inherit"49 id="resources-button"50 onClick={handleClick}51 endIcon={<KeyboardArrowDownIcon />}52 >53 Resources54 </Button>55 <Button color="inherit">About</Button>56 <Button color="inherit">Login</Button>57 </Stack>58 {/* Menu组件和锚点元素是分开的,通过Menu上的anchorEl属性来确定锚点元素。open属性决定是否显示Menu。onClose关闭时调用 */}59 <Menu60 id="resources-menu"61 anchorEl={anchorEl}62 open={open}63 onClose={handleClose}64 >65 <MenuItem onClick={handleClose}>Blog</MenuItem>66 <MenuItem onClick={handleClose}>Podcast</MenuItem>67 </Menu>68 </Toolbar>69 </AppBar>70 );71};效果:

在官方文档和老师的讲解中,button和menu都加上了id,button加上了aria相关的属性,这些属性是干什么的呢?
这是一个非常好的问题,它触及了现代前端开发中的一个核心概念:Web 无障碍性(Accessibility,简称 A11y)。
在您之前看到的
Menu示例中,给Button和Menu加上id和aria-属性,主要是为了确保使用屏幕阅读器或键盘导航的用户能够理解和操作这个菜单。1. 🔑 为什么需要
id属性?在这个特定的 Button/Menu 组合场景中,
id的作用是建立一个可识别的连接。
- 对于
Button(aria-controls):Button需要知道它控制的是哪个弹出的元素。这个 ID 就是用于引用菜单组件的。- 对于
Menu:Menu组件本身需要一个唯一的标识符,以便Button可以通过aria-controls属性指向它。2. ♿
Button上的aria-属性及其含义
aria-属性是 WAI-ARIA (Web Accessibility Initiative - Accessible Rich Internet Applications) 标准的一部分。它们不改变组件的外观,但会向屏幕阅读器等辅助技术传达关于组件的角色、状态和属性的信息。在您的
Button示例中,常见的 ARIA 属性及其含义如下:
ARIA 属性 含义 (作用) 屏幕阅读器读出 (示例) aria-controls指向一个由当前元素控制或分组的元素的 id。“菜单按钮,控制 [菜单ID]” aria-haspopup表示该元素会弹出一个浮动元素(如菜单、对话框)。 “菜单按钮,有弹出菜单” aria-expanded表示它控制的可展开元素当前的状态(是否展开)。 当 open为true时读出:“展开”详细解释:
aria-controls={open ? 'resources-menu' : undefined}
- 告诉辅助技术:当这个按钮被激活时,它会控制一个 ID 为
'resources-menu'的区域。这对于用户理解界面结构至关重要。
aria-haspopup="true"(或指定为menu)
- 告诉辅助技术:点击这个按钮会导致一个弹出式菜单出现,而不是进行页面导航或执行普通操作。
aria-expanded={open ? 'true' : undefined}
- 关键状态属性:当菜单是打开状态时,
aria-expanded应该设为"true";当菜单关闭时,设为"false"或undefined。- 这能让屏幕阅读器用户知道,他们当前点击的按钮所关联的菜单是可见的还是隐藏的。
总结:
这些
id和aria-属性的复杂性,正是 MUI 致力于实现高质量无障碍性的体现。它们是确保任何用户,无论是否有残障,都能有效使用您的 Web 应用界面的关键。
xxxxxxxxxx411<Stack spacing={2} direction="row">2 <Button color="inherit">Features</Button>3 <Button color="inherit">Pricing</Button>4 {/* 新增按钮,设置点击事件,设置尾部的图标 */}5 <Button6 color="inherit"7 id="resources-button"8 onClick={handleClick}9 endIcon={<KeyboardArrowDownIcon />}10 aria-controls={open ? "resources-menu" : undefined}11 aria-haspopup="true"12 >13 Resources14 </Button>15 <Button color="inherit">About</Button>16 <Button color="inherit">Login</Button>17</Stack>18{/* Menu组件和锚点元素是分开的,通过Menu上的anchorEl属性来确定锚点元素。open属性决定是否显示Menu。onClose关闭时调用 */}19<Menu20 id="resources-menu"21 anchorEl={anchorEl}22 open={open}23 onClose={handleClose}24 slotProps={{25 list: {26 "aria-labelledby": "resources-button",27 },28 }}29 // anchorOrigin 和 transformOrigin属性用来调整menu出现的位置30 anchorOrigin={{31 vertical: "bottom",32 horizontal: "right",33 }}34 transformOrigin={{35 vertical: "top",36 horizontal: "right",37 }}38 >39 <MenuItem onClick={handleClose}>Blog</MenuItem>40 <MenuItem onClick={handleClose}>Podcast</MenuItem>41</Menu>如果对anchor position不是很理解,可以参考官网:https://mui.com/material-ui/react-popover/#anchor-playground
The Link component allows you to easily customize anchor elements with your theme colors and typography styles.
Link组件可以用在breadcrumbs中。
xxxxxxxxxx211import { Stack, Link } from "@mui/material";23export const MuiLink = () => {4 return (5 <Stack spacing={2} direction="row" m={4}>6 <Link href="#">Link</Link>78 {/* 可以使用underline属性来设置下划线,color属性来设置颜色 */}9 <Link href="#" underline="none" color="secondary">10 underline11 </Link>1213 {/* 可以将Link组件作为Typography的child,使用variant属性来设置Link组件的样式 */}14 <Typography variant="h6">15 <Link href="#" underline="none">16 h617 </Link>18 </Typography>19 </Stack>20 );21};

A breadcrumbs is a list of links that help visualize a page's location within a site's hierarchical structure, it allows navigation up to any of the ancestors.
xxxxxxxxxx231// mycode\react-mui-demo\src\components\MuiBreadcrumbs.tsx23import { Box, Link, Breadcrumbs, Typography } from "@mui/material";45export const MuiBreadcrumbs = () => {6 return (7 <Box m={2}>8 {/* Breadcrumbs会自动添加separators */}9 <Breadcrumbs>10 <Link underline="hover" href="#">11 Home12 </Link>13 <Link underline="hover" href="#">14 Catalog15 </Link>16 <Link underline="hover" href="#">17 Accessories18 </Link>19 <Typography color="text.primary">Shoes</Typography>20 </Breadcrumbs>21 </Box>22 );23};效果:

xxxxxxxxxx31<Breadcrumbs separator="-">2......3</Breadcrumbs>
xxxxxxxxxx51import NavigateNextIcon from "@mui/icons-material/NavigateNext";23<Breadcrumbs separator={<NavigateNextIcon fontSize="small" />}>4......5</Breadcrumbs>
xxxxxxxxxx61<Breadcrumbs2 separator={<NavigateNextIcon fontSize="small" />}3 maxItems={2}4 >5 ......6</Breadcrumbs>可以看到,只有两个Item显示了,并且点击省略号之后会显示全部。

The navigation drawers (or "sidebars") provide ergonomic access to destinations in a site or app functionality such as switching accounts.
导航抽屉(或侧边栏)提供符合人体工程学的访问方式,方便用户访问网站或应用程序的功能,例如切换账户。
mui里面的drawer不单单只是抽屉,还包括(临时的或永久的)侧边栏,就类似CRM里面的侧边菜单栏,这一点要记住。
现在只学习简单的抽屉。
xxxxxxxxxx381// 23import { Drawer, IconButton, Box, Typography } from "@mui/material";4import { useState } from "react";5import MenuIcon from "@mui/icons-material/Menu";67export const MuiDrawer = () => {8 const [isDrawerOpen, setIsDrawerOpen] = useState(false);9 return (10 <>11 <IconButton12 size="large"13 edge="start"14 color="inherit"15 aria-label="logo"16 sx={{17 marginLeft: "10px",18 }}19 onClick={() => setIsDrawerOpen(true)}20 >21 <MenuIcon />22 </IconButton>23 {/* Use the anchor prop to specify which side of the screen the Drawer should originate from. */}24 {/* open属性决定抽屉打开与否 */}25 <Drawer26 anchor="left"27 open={isDrawerOpen}28 onClose={() => setIsDrawerOpen(false)}29 >30 <Box p={2} width="250px" textAlign="center" role="presentation">31 <Typography variant="h6" component="div">32 Side Panel33 </Typography>34 </Box>35 </Drawer>36 </>37 );38};效果:

When pressed, a floating action button can display three to six related actions in the form of a Speed Dial.
按下后,一个浮动操作按钮可以以快速拨号的形式显示三到六个相关操作。根据老师的说法,这个组件更适合移动端。
就是下面这种组件:

xxxxxxxxxx471// mycode\react-mui-demo\src\components\MuiSpeedDial.tsx23import { SpeedDial, SpeedDialIcon, SpeedDialAction } from "@mui/material";4import ContentCopyOutlinedIcon from "@mui/icons-material/ContentCopyOutlined";5import PrintIcon from "@mui/icons-material/Print";6import ShareIcon from "@mui/icons-material/Share";78export const MuiSpeedDial = () => {9 return (10 // icon属性指定speeddial的图标11 <SpeedDial12 ariaLabel="Navigation speed dial"13 sx={{14 position: "absolute",15 bottom: 16,16 right: 16,17 }}18 icon={<SpeedDialIcon />}19 >20 {/* icon属性指定操作按钮的图标,tooltip.title指定操作按钮悬浮的提示信息 */}21 <SpeedDialAction22 icon={<ContentCopyOutlinedIcon />}23 slotProps={{24 tooltip: {25 title: "Copy",26 },27 }}28 />29 <SpeedDialAction30 icon={<PrintIcon />}31 slotProps={{32 tooltip: {33 title: "Print",34 },35 }}36 />37 <SpeedDialAction38 icon={<ShareIcon />}39 slotProps={{40 tooltip: {41 title: "Share",42 },43 }}44 />45 </SpeedDial>46 );47};使用模拟手机端的屏幕来看效果:

xxxxxxxxxx101<SpeedDialAction2 icon={<ContentCopyOutlinedIcon />}3 slotProps={{4 tooltip: {5 title: "Copy",6 // open属性可以设置为true,这样就会一直显示tooltip7 open: true,8 },9 }}10 />
在显示action buttons之后,更换speeddial里面的图标:
xxxxxxxxxx111<SpeedDial2 ariaLabel="Navigation speed dial"3 sx={{4 position: "absolute",5 bottom: 16,6 right: 16,7 }}8 // 设置openIcon属性,更换打开后的图标9 icon={<SpeedDialIcon openIcon={<EditIcon />} />}10 >11</SpeedDial>
这个组件比较适合手机或平板页面,类似于app里面的底部导航栏。
xxxxxxxxxx291// mycode\react-mui-demo\src\components\MuiBottomNavigation.tsx23import { BottomNavigation, BottomNavigationAction } from "@mui/material";4import HomeIcon from "@mui/icons-material/Home";5import FavoriteIcon from "@mui/icons-material/Favorite";6import PersonIcon from "@mui/icons-material/Person";7import { useState } from "react";89export const MuiBottomNavigation = () => {10 const [value, setValue] = useState(0);1112 return (13 // 如果底部导航栏的items数量比较少,那么建议使用showLabels,来显示图标的文字。只需要在BottomNavigation上设置value和change事件,即可触发BottomNavigationAction的变化14 <BottomNavigation15 sx={{16 width: "100%",17 position: "absolute",18 bottom: 0,19 }}20 value={value}21 onChange={(_e, newVal) => setValue(newVal)}22 showLabels23 >24 <BottomNavigationAction label="Home" icon={<HomeIcon />} />25 <BottomNavigationAction label="Favorite" icon={<FavoriteIcon />} />26 <BottomNavigationAction label="Profile" icon={<PersonIcon />} />27 </BottomNavigation>28 );29};
第28-33节,是data display相关的内容。
xxxxxxxxxx511// mycode\react-mui-demo\src\components\MuiAvatar.tsx23import { Stack, Avatar } from "@mui/material";45export const MuiAvatar = () => {6 return (7 <Stack spacing={4}>8 {/* 默认样式 */}9 <Stack spacing={2} direction="row">10 <Avatar>BW</Avatar>11 <Avatar>CK</Avatar>12 </Stack>1314 <Stack spacing={2} direction="row">15 {/* 添加背景色 */}16 <Avatar sx={{ backgroundColor: "primary.light" }}>BW</Avatar>17 <Avatar sx={{ backgroundColor: "success.light" }}>CK</Avatar>18 {/* 使用 width 和 height 来设置大小 */}19 <Avatar20 sx={{ backgroundColor: "primary.light", width: 24, height: 24 }}21 >22 BW23 </Avatar>24 <Avatar25 sx={{ backgroundColor: "success.light", width: 48, height: 48 }}26 >27 CK28 </Avatar>29 {/* 使用variant属性来设置头像不同的形式 */}30 <Avatar variant="square" sx={{ backgroundColor: "primary.light" }}>31 BW32 </Avatar>33 <Avatar variant="rounded" sx={{ backgroundColor: "success.light" }}>34 CK35 </Avatar>36 </Stack>3738 {/* 指定图片 */}39 <Stack spacing={2} direction="row">40 <Avatar41 src="https://randomuser.me/api/portraits/women/79.jpg"42 alt="Jane Doe"43 />44 <Avatar45 src="https://randomuser.me/api/portraits/men/51.jpg"46 alt="John Doe"47 />48 </Stack>49 </Stack>50 );51};
Badge generates a small badge to the top-right of its child(ren).
这句话说明了,badge要显示在某个元素右上角,那么需要使用Badge组件来包裹这个元素,而不是相反。
xxxxxxxxxx501// mycode\react-mui-demo\src\components\MuiBadge.tsx23import { Stack, Badge } from "@mui/material";4import MailIcon from "@mui/icons-material/Mail";56export const MuiBadge = () => {7 return (8 <Stack spacing={4}>9 <Stack direction="row" spacing={2}>10 {/* 基本用法,badgeContent是badge的内容,color用来设置颜色 */}11 <Badge badgeContent={5} color="primary">12 <MailIcon />13 </Badge>14 </Stack>1516 <Stack direction="row" spacing={2}>17 {/* 当badgeContent的值为0时,默认不显示badge */}18 <Badge badgeContent={0} color="secondary">19 <MailIcon />20 </Badge>21 {/* 当badgeContent的值为0时,可以设置showZero,这样就可以显示badge */}22 <Badge badgeContent={0} color="secondary" showZero>23 <MailIcon />24 </Badge>25 </Stack>2627 <Stack direction="row" spacing={2}>28 {/* 当badgeContent的值超过99时,默认显示的就是99+ */}29 <Badge badgeContent={100} color="error">30 <MailIcon />31 </Badge>32 {/* 但可以设置max参数,只要不超过这个参数值,就可以完整显示 */}33 <Badge badgeContent={500} max={999} color="error">34 <MailIcon />35 </Badge>36 </Stack>3738 <Stack direction="row" spacing={2}>39 {/* 可以设置badge变体为dot,这时候就不需要设置badgeContent属性了,因为此时就是一个点 */}40 <Badge variant="dot" color="warning">41 <MailIcon />42 </Badge>43 {/* variant="dot"时,可以指定invisible属性,来决定是否显示这个badge */}44 <Badge variant="dot" invisible={true} color="primary">45 <MailIcon />46 </Badge>47 </Stack>48 </Stack>49 );50};效果:

Lists are continuous, vertical indexes of text or images.
List组件还是蛮重要的,单纯的用List好像没有必要,就好像我之前写vue项目的时候,遇到垂直方向上很多item的情况,都是直接div+v-for。
不能再像那样写代码了,能够用到UI组件的地方,就要想到UI组件,因为这些组件绑定了很多样式和方法,直接使用会更加简单,别人看代码也更容易理解。
List可以做侧边栏或菜单栏:

可以做选项列表:

总之,遇到垂直方向上很多item的,就要想到使用List。
List涉及到的组件特别多,到时候参考文档即可。
xxxxxxxxxx541// 23import {4 Box,5 List,6 ListItem,7 ListItemText,8 ListItemButton,9 ListItemIcon,10 Divider,11 ListItemAvatar,12 Avatar,13} from "@mui/material";14import InboxIcon from "@mui/icons-material/Inbox";1516export const MuiList = () => {17 return (18 <Box sx={{ width: "400px", bgcolor: "#efefef" }}>19 <List>20 {/* disablePadding 去除list的padding */}21 <ListItem disablePadding>22 {/* ListItemButton将ListItem变为按钮 */}23 <ListItemButton>24 <ListItemIcon>25 <Avatar>26 <InboxIcon />27 </Avatar>28 </ListItemIcon>29 {/* primary是listitem的主要文字,secondary是listitem的次要文字 */}30 <ListItemText primary="List item 1" />31 </ListItemButton>32 </ListItem>33 {/* Divider为ListItem之间添加间隔样式 */}34 <Divider />35 <ListItem>36 <ListItemAvatar>37 <Avatar>38 <InboxIcon />39 </Avatar>40 </ListItemAvatar>41 <ListItemText primary="List item 2" secondary="Secondary text" />42 </ListItem>43 <ListItem>44 <ListItemAvatar>45 <Avatar>46 <InboxIcon />47 </Avatar>48 </ListItemAvatar>49 <ListItemText primary="List item 3" />50 </ListItem>51 </List>52 </Box>53 );54};
Chips are compact elements that represent an input, attribute, or action.Chips allow users to enter information, make selections, filter content, or trigger actions.
chip,可以翻译为字片,是一种紧凑、可交互的元素,主要用于表示信息片段、用户选择、属性标签或简短操作。最常见的就是多选框中,如果选择了某些项,那么就会以chip的方式显示在多选框中。

chips也可以用来显示属性,比如说我会哪些技能,html、css、javascript、typescript、react、vue等等就可以用chips来展现。
xxxxxxxxxx471// mycode\react-mui-demo\src\components\MuiChip.tsx23import { Stack, Chip, Avatar } from "@mui/material";4import FaceIcon from "@mui/icons-material/face";5import { useState } from "react";67export const MuiChip = () => {8 const [chips, setChips] = useState(["chip 1", "chip 2", "chip 3"]);910 const handleDelete = (itemDeleted: string) => {11 setChips(chips.filter((chip) => chip !== itemDeleted));12 };13 return (14 <Stack spacing={4}>15 <Stack direction="row" spacing={2}>16 {/* 默认用法,设置label即可 */}17 <Chip label="Chip" />18 {/* 设置color, size属性,调整样式 */}19 <Chip label="Chip" color="primary" size="small" />20 {/* 设置变体 */}21 <Chip label="Chip outlined" color="secondary" variant="outlined" />22 {/* 设置头像 */}23 <Chip label="Avatar" avatar={<Avatar>V</Avatar>} />24 {/* 设置图片 */}25 <Chip label="Icon" icon={<FaceIcon />} color="warning" />2627 {/* 设置onClick事件,让Chip变得可以点击 */}28 <Chip label="Click" color="error" onClick={() => alert("clicked")} />2930 {/* 设置onDelete事件,Chip就会自动出现一个删除图标 */}31 <Chip32 label="Delete"33 color="success"34 onClick={() => alert("delete clicked")}35 onDelete={() => alert("delete is deleted")}36 />37 </Stack>3839 <Stack direction="row" spacing={2}>40 {/* Chip array的操作 */}41 {chips.map((chip) => (42 <Chip key={chip} label={chip} onDelete={() => handleDelete(chip)} />43 ))}44 </Stack>45 </Stack>46 );47};效果:
