variant的本意是变体、变种。在计算机里面可以翻译为变体,也就是说同一种物体,有很多种不同的形态,比如说人有黑人、白人、黄种人多种变体,但都是人。这个概念非常重要,因为在UI框架里面,variant的概念无处不在。
什么是material ui?就是一个UI组件库。Material UI (现在通常简称为 MUI) 的设计灵感和风格来源于 Google 的 Material Design 规范。

课程结构

创建项目npm create vite@latest react-mui-demo。
安装依赖npm install @mui/material @emotion/react @emotion/styled,@mui/material是核心库,提供组件。@emotion/react和@emotion/styled让我们可以在js里面写css。
将App.tsx文件改为下面这样,方便后面的学习:
xxxxxxxxxx121import './App.css'23function App() {45 return (6 <>78 </>9 )10}1112export default App后面每讲到一个component,都会放到App.tsx里面来演示。
mui的文档应该怎么查看:
在components里面可以看到各种组件的用法,但是里面的属性含义是不明的。
在components api里面能够查看组件属性的含义:
所以二者要结合起来看。
第3-13节是Inputs相关的内容。
typography:排版。
这个组件我之前从来没有用过,也好像没有见过,到底这个是用来干什么的呢?问了一下gemini,它说:
Typography是一个内容呈现组件,专注于文字本身。在构建应用时,您会大量使用其他 MUI 组件(如Button、Paper、Grid等)和普通 HTML 标签(如<div>),它们负责结构、布局和交互。只有当您直接在这些结构中添加纯文本时,才应该使用Typography来包裹。
就是说,当有文字展现的时候,就要考虑使用Typography来包裹住,以保持文字样式的统一,避免写很多样式代码。Typography主要设置了字体大小、字重和line-height。
仔细想一想,还是蛮有用的,我在写vue项目的时候,确实写了很多文字相关的样式,这些样式其实都可以预定义,只不过项目做的很粗糙,自己又不敢做,所以一直就没有做。
这个组件在有要求的项目里面,还是很重要的。
Use typography to present your design and content as clearly and efficiently as possible.
xxxxxxxxxx241// 23import { Typography } from '@mui/material'45export const MuiTypography = () => {6 return (7 <div>8 <Typography variant='h1'>h1 Heading</Typography>9 <Typography variant='h2'>h2 Heading</Typography>10 <Typography variant='h3'>h3 Heading</Typography>11 <Typography variant='h4'>h4 Heading</Typography>12 <Typography variant='h5'>h5 Heading</Typography>13 <Typography variant='h6'>h6 Heading</Typography>1415 {/* subtitle1 和 subtitle2 h6更小一些,就像它们的名称那样,可以作为更小的副标题 */}16 <Typography variant='subtitle1'>Sub title 1</Typography>17 <Typography variant='subtitle2'>Sub title 2</Typography>1819 {/* body1 和 body2 指的是p标签,使用了不同的样式 */}20 <Typography variant='body1'>Lorem ipsum dolor sit amet consectetur adipisicing elit. Incidunt tempora reiciendis corporis quam voluptates officia earum amet fuga quisquam magnam. Ut officiis provident incidunt consequuntur perspiciatis quisquam similique magnam molestias.</Typography>21 <Typography variant='body2'>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Mollitia ex ullam consequatur unde aspernatur facilis dolorum voluptates molestiae cupiditate hic, commodi voluptas optio placeat, suscipit quisquam omnis dolor asperiores accusamus.</Typography>22 </div>23 )24}

可以看到,Typography所展示的是原始的标签,而不是一些div+css。
老师讲解了一下mui组件的默认主题样式从哪里可以找到,在https://mui.com/material-ui/customization/default-theme/里面可以看到:

xxxxxxxxxx51{/* gutterBottom用于指定margin-bottom,是boolean类型的值 */}2<Typography variant='h3' gutterBottom>h3 Heading</Typography>34{/* component用于指定这个Typography真正渲染成的标签或组件 */}5<Typography variant='h4' component="h1">h4 Heading</Typography>xxxxxxxxxx611// mycode\react-mui-demo\src\components\MuiButton.tsx23import { Stack, Button, IconButton } from "@mui/material"4import SendIcon from '@mui/icons-material/Send';56export const MuiButton = () => {7 return (8 <Stack spacing={4}>910 {/* Button基本用法,varient属性定义Button不同的变体 */}11 <Stack spacing={2} direction="row">12 {/* href属性,可以将这个button变为a标签 */}13 <Button variant="text" href="https://google.com">Text</Button>14 <Button variant="contained">Contained</Button>15 <Button variant="outlined">Outlined</Button>16 </Stack>1718 {/* Button的color属性,用来定义button的颜色 */}19 <Stack spacing={2} direction="row">20 <Button variant="contained" color="primary">primary</Button>21 <Button variant="contained" color="secondary">secondary</Button>22 <Button variant="contained" color="warning">warning</Button>23 <Button variant="contained" color="error">error</Button>24 <Button variant="contained" color="info">info</Button>25 <Button variant="contained" color="success">success</Button>26 </Stack>2728 {/* Button的size属性,用来定义button的大小 */}29 <Stack display="block" spacing={2} direction="row">30 <Button variant="contained" size="small">Small</Button>31 <Button variant="contained" size="medium">Medium</Button>32 <Button variant="contained" size="large">Large</Button>33 </Stack>343536 {/* 为Button添加icon,我们使用的是mui的icon,所以需要先安装依赖 */}37 {/* npm install @mui/icons-material */}38 <Stack spacing={2} direction="row">39 {/* 设置前置图标 */}40 <Button variant="contained" startIcon={<SendIcon />}>Send</Button>41 {/* 设置后缀图标 */}42 <Button variant="contained" endIcon={<SendIcon />}>Send</Button>4344 {/* 设置纯图标的按钮,使用IconButton组件,包裹一个Icon */}45 <IconButton aria-label="send" color="success" size="small">46 <SendIcon />47 </IconButton>48 </Stack>4950 <Stack spacing={2} direction="row">51 {/* 去掉阴影 */}52 <Button variant="contained" disableElevation>去掉阴影</Button>53 {/* 去掉ripple effect,就是水波纹效果 */}54 <Button variant="contained" disableRipple>去掉水波纹</Button>5556 {/* Button上使用onClick事件 */}57 <Button variant="outlined" color="primary" onClick={() => alert("send message")}>Send</Button>58 </Stack>59 </Stack>60 )61}老师演示了一下Button的用法。

button group的效果是这样的。遇到这种效果要记得使用它。

xxxxxxxxxx311// mycode\react-mui-demo\src\components\MuiButton.tsx23import { Stack, Button, IconButton, ButtonGroup } from "@mui/material"4import SendIcon from '@mui/icons-material/Send';56export const MuiButton = () => {7 return (8 <Stack spacing={4}>9 ......10 11 <Stack display="block">12 {/* 在使用ButtonGroup的时候,variant属性就不要定义在单独的Button上面了,而是要定义在ButtonGroup上面,以达到统一的效果 */}13 <ButtonGroup variant="outlined">14 <Button>Left</Button>15 <Button>Center</Button>16 <Button>Right</Button>17 </ButtonGroup>18 </Stack>1920 <Stack display="block">21 {/* 使用orientation来定义Button的排列方向,size来定义按钮大小,color来定义按钮颜色,建议加上aria-label属性 */}22 <ButtonGroup variant="contained" orientation="vertical" size="small" color="secondary" aria-label="group button">23 {/* onClick事件要定义在单独的Button上面 */}24 <Button onClick={() => alert("left clicked")}>Left</Button>25 <Button>Center</Button>26 <Button>Right</Button>27 </ButtonGroup>28 </Stack>29 </Stack>30 )31}
toggle button的形式是下面这样的:

而我脑海中出现的toggle button,其实叫做switch。

xxxxxxxxxx331// mycode\react-mui-demo\src\components\MuiButton.tsx23import { Stack, Button, IconButton, ButtonGroup, ToggleButtonGroup, ToggleButton } from "@mui/material"4import SendIcon from '@mui/icons-material/Send';5import FormatBoldIcon from '@mui/icons-material/FormatBold';6import FormatItalicIcon from '@mui/icons-material/FormatItalic';7import FormatUnderlinedIcon from '@mui/icons-material/FormatUnderlined';8import { useState } from "react";910export const MuiButton = () => {11 const [formats, setFormats] = useState<string[]>([]);1213 console.log("formats = ", formats);1415 const handleFormatChange = (_event: React.MouseEvent<HTMLElement>, updatedFormats: string[]) => {16 setFormats(updatedFormats);17 }1819 return (20 <Stack spacing={4}>21 ......2223 <Stack direction="row">24 {/* 使用value绑定值,onChange事件处理点击事件,size设置大小,color设置颜色,orientation设置方向 */}25 <ToggleButtonGroup size="small" color="primary" aria-label="text-format" value={formats} onChange={handleFormatChange}>26 <ToggleButton value="bold" aria-label="bold"><FormatBoldIcon /></ToggleButton>27 <ToggleButton value="italic" aria-label="italic"><FormatItalicIcon /></ToggleButton>28 <ToggleButton value="underlined" aria-label="underlined"><FormatUnderlinedIcon /></ToggleButton>29 </ToggleButtonGroup>30 </Stack>31 </Stack>32 )33}效果:

需要添加exclusive属性,而且变量值也变为单个值。
xxxxxxxxxx331// 23import { Stack, Button, IconButton, ButtonGroup, ToggleButtonGroup, ToggleButton } from "@mui/material"4import SendIcon from '@mui/icons-material/Send';5import FormatBoldIcon from '@mui/icons-material/FormatBold';6import FormatItalicIcon from '@mui/icons-material/FormatItalic';7import FormatUnderlinedIcon from '@mui/icons-material/FormatUnderlined';8import { useState } from "react";910export const MuiButton = () => {11 const [formats, setFormats] = useState<string>("");1213 console.log("formats = ", formats);1415 const handleFormatChange = (_event: React.MouseEvent<HTMLElement>, updatedFormats: string) => {16 setFormats(updatedFormats);17 }1819 return (20 <Stack spacing={4}>21 ......2223 <Stack direction="row">24 {/* 使用value绑定值,onChange事件处理点击事件,size设置大小,color设置颜色,orientation设置方向 */}25 <ToggleButtonGroup size="small" exclusive color="primary" aria-label="text-format" value={formats} onChange={handleFormatChange}>26 <ToggleButton value="bold" aria-label="bold"><FormatBoldIcon /></ToggleButton>27 <ToggleButton value="italic" aria-label="italic"><FormatItalicIcon /></ToggleButton>28 <ToggleButton value="underlined" aria-label="underlined"><FormatUnderlinedIcon /></ToggleButton>29 </ToggleButtonGroup>30 </Stack>31 </Stack>32 )33}效果:

xxxxxxxxxx311// mycode\react-mui-demo\src\components\MuiTextField.tsx23import { TextField, Stack } from "@mui/material"456export const MuiTextField = () => {7 return (8 <Stack spacing={4}>9 <Stack direction="row" spacing={2}>10 {/* outlined是默认的变体 */}11 <TextField label="Name" variant="outlined" />12 <TextField label="Name" variant="filled" />13 <TextField label="Name" variant="standard" />14 </Stack>15 <Stack direction="row" spacing={2}>16 {/* size控制大小,color控制颜色 */}17 <TextField label="Small" size="small" color="success" />18 </Stack>19 <Stack direction="row" spacing={2}>20 {/* 当TextField使用在form中时,使用required属性来设置必填 */}21 <TextField label="Form input" required />22 {/* 使用type属性来设置TextField的类型,使用helperText来设置帮助信息,当helperText结合errors使用时,可以当作报错信息 */}23 <TextField label="Password" type="password" helperText="Do not share you password with anyone" />24 {/* 使用disabled属性设置不可交互 */}25 <TextField label="Disabled" defaultValue="disabled" disabled />26 {/* 使用slotProps里面的input属性,设置只读 */}27 <TextField label="Read only" slotProps={{ input: { readOnly: true } }} />28 </Stack>29 </Stack>30 )31}
需要结合InputAdornment组件一起使用。
xxxxxxxxxx251// mycode\react-mui-demo\src\components\MuiTextField.tsx23import { TextField, Stack, InputAdornment } from "@mui/material"456export const MuiTextField = () => {7 return (8 <Stack spacing={4}>9 ......10 11 <Stack direction="row" spacing={2}>12 <TextField label="Amount" slotProps={{13 input: {14 startAdornment: <InputAdornment position="start">$</InputAdornment>15 }16 }} />17 <TextField label="Weight" slotProps={{18 input: {19 endAdornment: <InputAdornment position="end">kg</InputAdornment>20 }21 }} />22 </Stack>23 </Stack>24 )25}
这个是在form里面很重要的用法。
xxxxxxxxxx181// mycode\react-mui-demo\src\components\MuiTextField.tsx23import { TextField, Stack, InputAdornment } from "@mui/material"4import { useState } from "react"56export const MuiTextField = () => {7 const [username, setUsername] = useState("")89 return (10 <Stack spacing={4}>11 ......12 13 <Stack direction="row" spacing={2}>14 <TextField label="username" required value={username} onChange={e => setUsername(e.target.value)} error={!username} helperText={!username ? "required" : "please enter your username"} />15 </Stack>16 </Stack>17 )18}
可以看到,在页面初始化的时候就出现了必填校验报错,这个先不管,这不是mui的范畴。可以使用react hook form来解决表单问题。
实际上mui提供了Select组件,但是老师这里讲解的是使用TextField和MenuItem来组成Select。
xxxxxxxxxx221// mycode\react-mui-demo\src\components\MuiSelect.tsx23import { Box, TextField, MenuItem } from "@mui/material"4import { useState } from 'react'56export const MuiSelect = () => {7 const [country, setCountry] = useState("")8 console.log({ country });9 const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {10 setCountry(event.target.value as string);11 }12 return (13 <Box width={"250px"}>14 {/* 使用select属性来将TextField变成Select,fullWidth属性可以适配父级的宽度 */}15 <TextField label="select country" select fullWidth value={country} onChange={handleChange}>16 <MenuItem value="CN">China</MenuItem>17 <MenuItem value="US">United States</MenuItem>18 <MenuItem value="AU">Australia</MenuItem>19 </TextField>20 </Box>21 )22}
需要在TextField上添加属性,才能变为多选:
xxxxxxxxxx51slotProps={{2 select: {3 multiple: true4 }5}}案例:
xxxxxxxxxx281// mycode\react-mui-demo\src\components\MuiSelect.tsx23import { Box, TextField, MenuItem } from "@mui/material"4import { useState } from 'react'56export const MuiSelect = () => {7 const [countries, setCountries] = useState<string[]>([])8 console.log({ countries });9 const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {10 // 改成了多选之后,实际上e.target.value的值还是一个string,只不过用英文逗号隔开了,所以需要下面的处理11 const val = event.target.value;12 setCountries(typeof val === "string" ? val.split(",") : val);13 }14 return (15 <Box width={"250px"}>16 {/* 使用select属性来将TextField变成Select,fullWidth属性可以适配父级的宽度 */}17 <TextField label="select countries" slotProps={{18 select: {19 multiple: true20 }21 }} select fullWidth value={countries} onChange={handleChange}>22 <MenuItem value="CN">China</MenuItem>23 <MenuItem value="US">United States</MenuItem>24 <MenuItem value="AU">Australia</MenuItem>25 </TextField>26 </Box>27 )28}
Radio组件可以单独使用,也可以组合起来使用。单独使用参考:https://mui.com/material-ui/react-radio-button/#standalone-radio-buttons。给出的是在Form里面的案例。
xxxxxxxxxx301// 23import { Box, FormControl, FormLabel, FormControlLabel, RadioGroup, Radio } from '@mui/material'4import { useState } from 'react'56export const MuiRadioButton = () => {7 const [year, setYear] = useState("")8 console.log({ year });910 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {11 setYear(e.target.value)12 }1314 return (15 <Box>16 <FormControl>17 <FormLabel id="job-experience-group-label">18 Year of experience19 </FormLabel>2021 <RadioGroup name="job-experience-group" value={year} onChange={handleChange} aria-labelledby="job-experience-group-label">22 <FormControlLabel control={<Radio />} label="0-2" value="0-2" />23 <FormControlLabel control={<Radio />} label="3-5" value="3-5" />24 <FormControlLabel control={<Radio />} label="6-10" value="6-10" />25 </RadioGroup>2627 </FormControl>28 </Box>29 )30}
xxxxxxxxxx511// 23import {4 Box,5 FormControl,6 FormLabel,7 FormControlLabel,8 RadioGroup,9 Radio,10 FormHelperText,11} from "@mui/material";12import { useState } from "react";1314export const MuiRadioButton = () => {15 const [year, setYear] = useState("");16 console.log({ year });1718 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {19 setYear(e.target.value);20 };2122 return (23 <Box>24 {/* 使用error来将label变为报警样式 */}25 <FormControl error>26 <FormLabel id="job-experience-group-label">27 Year of experience28 </FormLabel>2930 <RadioGroup31 name="job-experience-group"32 value={year}33 onChange={handleChange}34 aria-labelledby="job-experience-group-label"35 >36 {/* 可以设置 Radio 的大小和颜色 */}37 <FormControlLabel38 control={<Radio size="small" color="secondary" />}39 label="0-2"40 value="0-2"41 />42 <FormControlLabel control={<Radio />} label="3-5" value="3-5" />43 <FormControlLabel control={<Radio />} label="6-10" value="6-10" />44 </RadioGroup>4546 {/* 可以使用FormHelperText组件来显示error信息 */}47 <FormHelperText>Invalid selection</FormHelperText>48 </FormControl>49 </Box>50 );51};
为什么FormLabel上面要设置id,RadioGroup上面要设置aria-labelledby?
其实这还是无障碍设计的规范问题,而不是功能上的必须。
非常好的问题 👍。 这其实是一个 无障碍(Accessibility / a11y)设计 的规范问题,而不是功能上的必需。
🧩 一句话总结
<FormLabel>的id和<RadioGroup>的aria-labelledby是 配合使用的: 它们让屏幕阅读器(screen readers)知道 这组单选按钮的标题是什么。🧠 详细解释
<FormLabel>xxxxxxxxxx11<FormLabel id="demo-radio-buttons-group-label">Gender</FormLabel>
- 这个标签在视觉上是表单控件的标题,比如显示“Gender”。
- 设置
id是为了能被其他元素引用(比如下面的 RadioGroup)。
<RadioGroup>xxxxxxxxxx41<RadioGroup2aria-labelledby="demo-radio-buttons-group-label"3...4>
aria-labelledby是一个 ARIA 属性(Accessible Rich Internet Applications)。它告诉辅助技术(比如屏幕阅读器):
“这个 RadioGroup 的标签是由
id="demo-radio-buttons-group-label"的那个元素定义的。”也就是说:当屏幕阅读器读到这组单选按钮时,会说出:
“Gender, group of 3 radio buttons. Female, selected. Male. Other.”
✅ 如果你去掉它们,会怎样?
- 视觉上完全没问题。
- 但是对使用屏幕阅读器的人来说,他们不知道这组单选按钮的主题是什么(比如“Gender”)。
- 这会降低网站的 可访问性(Accessibility)等级。
💡 总结对照表
属性 用于 作用 idFormLabel 提供唯一标识 aria-labelledbyRadioGroup 引用上面的 id,告诉辅助技术它的标签是谁🌱 补充小知识
如果你用的是
label="Gender"这样的方式直接放在<RadioGroup>里,视觉上是一样的,但 MUI 官方更推荐使用 FormLabel + aria-labelledby,因为它能同时兼顾:
- 语义化结构(HTML)
- 无障碍兼容性(屏幕阅读器)
- 样式一致性(MUI 样式系统)
更多aria相关的问题,可以查看我专门的一个文档。
xxxxxxxxxx241// mycode\react-mui-demo\src\components\MuiCheckbox.tsx23import { Box, FormControlLabel, Checkbox } from "@mui/material";4import { useState } from "react";56export const MuiCheckbox = () => {7 const [accept, setAccept] = useState(false);8 console.log({ accept });910 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {11 setAccept(e.target.checked);12 };1314 return (15 <Box>16 <Box>17 <FormControlLabel18 label="I accept terms and conditions"19 control={<Checkbox checked={accept} onChange={handleChange} />}20 />21 </Box>22 </Box>23 );24};
xxxxxxxxxx321// mycode\react-mui-demo\src\components\MuiCheckbox.tsx23import { Box, FormControlLabel, Checkbox } from "@mui/material";4import { useState } from "react";5import BookmarkIcon from "@mui/icons-material/Bookmark";6import BookmarkBorderIcon from "@mui/icons-material/BookmarkBorder";78export const MuiCheckbox = () => {9 const [accept, setAccept] = useState(false);10 console.log({ accept });1112 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {13 setAccept(e.target.checked);14 };1516 return (17 <Box>18 ......1920 <Box>21 {/* 纯图标的选择框 */}22 <Checkbox23 icon={<BookmarkBorderIcon />}24 checkedIcon={<BookmarkIcon />}25 checked={accept}26 onChange={handleChange}27 />28 </Box>29 </Box>30 );31};32
这种checkbox的交互方式要记住。
xxxxxxxxxx821// mycode\react-mui-demo\src\components\MuiCheckbox.tsx23import {4 Box,5 FormControlLabel,6 Checkbox,7 FormLabel,8 FormControl,9 FormGroup,10} from "@mui/material";11import { useState } from "react";12import BookmarkIcon from "@mui/icons-material/Bookmark";13import BookmarkBorderIcon from "@mui/icons-material/BookmarkBorder";1415export const MuiCheckbox = () => {16 const [accept, setAccept] = useState(false);17 // console.log({ accept });18 19 // 设置checkbox的值为一个数组,但是这个值不是直接绑定到某个元素上的,而是由多个checkbox共同决定的。这种方式要记住。20 const [skills, setSkills] = useState<string[]>([]);21 console.log({ skills });2223 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {24 setAccept(e.target.checked);25 };2627 const handleSkillChange = (e: React.ChangeEvent<HTMLInputElement>) => {28 const index = skills.indexOf(e.target.value);29 if (index === -1) {30 setSkills([skills, e.target.value]);31 } else {32 setSkills(skills.filter((c) => c !== e.target.value));33 }34 };3536 return (37 <Box>38 ......3940 {/* checkbox组 */}41 <Box>42 <FormControl>43 <FormLabel>Skills</FormLabel>44 <FormGroup>45 <FormControlLabel46 label="HTML"47 value="html"48 control={49 <Checkbox50 checked={skills.includes("html")}51 onChange={handleSkillChange}52 />53 }54 />5556 <FormControlLabel57 label="CSS"58 value="css"59 control={60 <Checkbox61 checked={skills.includes("css")}62 onChange={handleSkillChange}63 />64 }65 />6667 <FormControlLabel68 label="JavaScript"69 value="javascript"70 control={71 <Checkbox72 checked={skills.includes("javascript")}73 onChange={handleSkillChange}74 />75 }76 />77 </FormGroup>78 </FormControl>79 </Box>80 </Box>81 );82};
xxxxxxxxxx21{/* row属性可以让FormGroup里面的元素排列成行 */}2<FormGroup row></FormGroup>
xxxxxxxxxx121{/* checkbox上的size和color属性 */}2<FormControlLabel3 label="I accept terms and conditions"4 control={5 <Checkbox6 checked={accept}7 size="small"8 color="error"9 onChange={handleChange}10 />11 }12/>
xxxxxxxxxx281// mycode\react-mui-demo\src\components\MuiSwitch.tsx23import { Box, FormControlLabel, Switch } from "@mui/material";4import { useState } from "react";56export const MuiSwitch = () => {7 const [checked, setChecked] = useState(false);8 console.log({ checked });910 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {11 setChecked(e.target.checked);12 };13 return (14 <Box>15 <FormControlLabel16 label="dark mode"17 control={18 <Switch19 checked={checked}20 onChange={handleChange}21 size="small"22 color="secondary"23 />24 }25 />26 </Box>27 );28};
xxxxxxxxxx211// mycode\react-mui-demo\src\components\MuiRating.tsx23import { Stack, Rating } from "@mui/material";4import { useState } from "react";56export const MuiRating = () => {7 const [rate, setRate] = useState<number | null>(null);8 console.log({ rate });910 const handleChange = (11 _e: React.ChangeEvent<object>,12 newValue: number | null13 ) => {14 setRate(newValue);15 };16 return (17 <Stack spacing={2}>18 <Rating value={rate} onChange={handleChange} />19 </Stack>20 );21};在点亮的星星上面再次点击一次,就会取消高亮。

xxxxxxxxxx311// mycode\react-mui-demo\src\components\MuiRating.tsx23import { Stack, Rating } from "@mui/material";4import { useState } from "react";5import FavoriteIcon from "@mui/icons-material/Favorite";6import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder";78export const MuiRating = () => {9 const [rate, setRate] = useState<number | null>(null);10 console.log({ rate });1112 const handleChange = (13 _e: React.ChangeEvent<object>,14 newValue: number | null15 ) => {16 setRate(newValue);17 };18 return (19 <Stack spacing={2}>20 {/* precision=0.5表示可以只选半个,size控制大小,icon和emptyIcon用来替换图标 */}21 <Rating22 value={rate}23 onChange={handleChange}24 precision={0.5}25 size="large"26 icon={<FavoriteIcon fontSize="inherit" />}27 emptyIcon={<FavoriteBorderIcon fontSize="inherit" />}28 />29 </Stack>30 );31};
还有readOnly只读属性。highlightSelectedOnly属性,可以只将当前选项高亮,这个在rating的图标都不相同的时候,可以使用。
xxxxxxxxxx241// mycode\react-mui-demo\src\components\MuiAutocomplete.tsx23import { Stack, Autocomplete, TextField } from "@mui/material";4import { useState } from "react";56const skills = ["HTML", "CSS", "JavaScript", "TypeScript", "React"];78export const MuiAutocomplete = () => {9 const [value, setValue] = useState<string | null>(null);10 console.log({ value });1112 return (13 <Stack spacing={2} width="250px">14 <Autocomplete15 options={skills}16 renderInput={(params) => <TextField {params} label="Skills" />}17 value={value}18 onChange={(_e: React.ChangeEvent<object>, newVal: string | null) =>19 setValue(newVal)20 }21 />22 </Stack>23 );24};可以看到,里面可以搜索。而且只有选中的选项才会有值,如果是输入的,那么就没有值。这个值可以供校验来判断,或者最后提交的时候来校验判断,要确保用户提交的是正确的数据。

freeSolo属性在一些情况下,比如说google搜索里面,是给出了搜索建议,但是我还是可以自己输入内容进行搜索,自己输入内容后点击enter键,此时输入的内容也是有效的,就像这样:

想要这种效果,就在Autocomplete组件上加上freeSolo属性。
xxxxxxxxxx91<Autocomplete2 options={skills}3 renderInput={(params) => <TextField {params} label="Skills" />}4 value={value}5 onChange={(_e: React.ChangeEvent<object>, newVal: string | null) =>6 setValue(newVal)7 }8 freeSolo9/>
options参数可以直接给对象数组,对象数组必须包含label属性。如果不包含,那么需要设置getOptionLabel属性。

里面的value要进行处理,value的值类型就是对象数组里面对象的类型,其余的没有什么变化。
xxxxxxxxxx451// mycode\react-mui-demo\src\components\MuiAutocomplete.tsx23import { Stack, Autocomplete, TextField } from "@mui/material";4import { useState } from "react";56type Skill = {7 id: number;8 label: string;9};1011const skills = ["HTML", "CSS", "JavaScript", "TypeScript", "React"];1213const skillOptions = skills.map((skill, index) => ({14 id: index + 1,15 label: skill,16}));1718export const MuiAutocomplete = () => {19 const [value, setValue] = useState<string | null>(null);20 const [skill, setSkill] = useState<Skill | null>(null);21 console.log({ skill });2223 return (24 <Stack spacing={2} width="250px">25 <Autocomplete26 options={skills}27 renderInput={(params) => <TextField {params} label="Skills" />}28 value={value}29 onChange={(_e: React.ChangeEvent<object>, newVal: string | null) =>30 setValue(newVal)31 }32 freeSolo33 />34 <Autocomplete35 options={skillOptions}36 renderInput={(params) => <TextField {params} label="Skills" />}37 value={skill}38 onChange={(_e: React.ChangeEvent<object>, newVal: Skill | null) =>39 setSkill(newVal)40 }41 />42 </Stack>43 );44};45可以看到,值是一个对象:

第14-27节,是layout相关的内容。
Box类似于div标签,如果没有特殊的语意,都使用Box来包裹其它组件。有特殊语意的组件,比如说 Container, Stack and Paper。Box的适用性更广泛一些。
xxxxxxxxxx131// 23import { Box } from "@mui/material";45export const MuiLayout = () => {6 return (7 <>8 <Box>codevolution</Box>9 {/* 可以使用component属性,让这个Box渲染成具体的标签组件 */}10 <Box component="span">span</Box>11 </>12 );13};Box默认渲染成div,但是如果指定component属性,就可以渲染成相应的组件。

为什么不直接使用div、span这些标签呢?因为Box可以设置sx属性,来使用mui定义好的主题样式(当然也可以直接定义样式),如下:
xxxxxxxxxx291// mycode\react-mui-demo\src\components\MuiLayout.tsx23import { Box } from "@mui/material";45export const MuiLayout = () => {6 return (7 <>8 <Box>codevolution</Box>9 {/* 可以使用component属性,让这个Box渲染成具体的标签组件 */}10 <Box component="span">span</Box>1112 {/* 设置 sx 属性,可以使用mui定义好的主题样式 */}13 <Box14 sx={{15 backgroundColor: "primary.main",16 color: "white",17 width: "100px",18 height: "100px",19 padding: "16px",20 "&:hover": {21 backgroundColor: "primary.light",22 },23 }}24 >25 Theme26 </Box>27 </>28 );29};
还可以直接在Box组件上设置样式属性:
xxxxxxxxxx141import { Box } from "@mui/material";23export const MuiLayout = () => {4 return (5 <>6 78 {/* 直接在Box上设置样式属性 */}9 <Box height="100px" width="100px" bgcolor="success.main" p={2}>10 Direct11 </Box>12 </>13 );14};
这里其实我感觉不是很爽,因为我感觉如果使用mui的样式的话,又要学习一套样式系统,我连tailwindcss都还没有搞清楚,学习这个确实很难说服自己。不过应该可以使用tailwind,样式先不用太着急。
Stack is a container component for arranging elements vertically or horizontally.
xxxxxxxxxx301// 23import { Box, Stack, Divider } from "@mui/material";45export const MuiLayout = () => {6 return (7 <Stack8 sx={{9 border: "1px solid red",10 }}11 direction="row"12 spacing={2}13 divider={<Divider orientation="vertical" flexItem />}14 >15 <Box>codevolution</Box>16 <Box component="span">span</Box>17 <Box18 sx={{19 20 }}21 >22 Theme23 </Box>2425 <Box height="100px" width="100px" bgcolor="success.main" p={2}>26 Direct27 </Box>28 </Stack>29 );30};重点放在Stack上面,sx用来设置样式,direction可以设置Stack里面元素排列的方向,spacing用来设置里面元素之间的间距,divider设置里面元素之间的分隔样式。

##