本文用于记录学习 Material UI 组件库。
本文目录:
- Material UI 简介与 Antd 的差异
- 安装 MUI
- 初探 MUI
- 文档与组件速览
- 自定义主题样式
- 亮/暗模式切换
当你习惯使用 Antd 组件库时,你或许也应该知道世界上另外一个优秀开源的 React 组件库:谷歌的 Material UI
Material UI 官网:https://mui.com/material-ui/
Material UI 的要求:
-
目前 Materia UI 版本为 V5,尚且部分支持 IE11,但等 V6 后就会不再支持 IE 11
关于浏览器兼容,目前已经不是前端需要发愁的一个问题了
-
服务端的 node.js 版本:支持最新的 node.js
-
React:需要 ^17.0.0
-
TypeScript:需要 ^3.5
关于 Material UI 的称呼:
在本文中可能将 Material UI 称呼为:
- MUI
- 材质UI
- 材质组件、材料组件
无论哪种称呼,在本文中你都知道是在说 Material UI 就好。
Material UI 与 Antd 的相同之处:
- 都是针对 React 框架,且组件丰富
- 同样优秀,目前 Antd 在 Github 上 86.9K 点赞、Material UI 是 87.8 K 点赞,两者相差无几
- 同样拥有活跃的社区和开发群体
Material UI 与 Antd 的差异:
-
公司不同:Antd 背后是 阿里集团,Material UI 背后是谷歌
-
组件自定义样式容易程度:
-
Antd 单个组件自定义样式相对不容易(比较复杂)
-
Material UI 自定义样式特别方便(这是它的优势)
但同时也意味着 Material UI 上手程度要比 Antd 更难一些
-
-
上手容易程度:
- 相对而言 Antd 更容易一些(或者你可以理解成 Antd 封装的更狠一些)
-
引入全局样式:Antd 需要引入,Material UI 不需要
-
简体中文文档:目前 Maturial UI 暂时没有中文版文档
-
Material UI 政治性:
-
Material UI 背后是谷歌,属于 "西方公司",在其官网顶部有一句话
MUI stands in solidarity with Ukraine. //翻译:MUI 和乌克兰站在同一立场。
额~,果然很西方
-
这种事不可能发生在 Antd 官网上。
如果你觉得 Material UI 很好用,但是也不要影响你的政治立场。
-
Mui组件库的关系:
在 https://mui.com/core/ 上我们可以看到,一共有 3 个组件库:
-
Base UI:最基础的组件库,包含交互逻辑但不包含 组件样式
-
Material UI:基于 Base UI 和 Material Design 设计规范,包含默认样式
Material Design 是谷歌推出的一套 适用于 手机、平板电脑、台式电脑 一致性的一套设计规范。
-
Joy UI:基于 Base UI 但是不遵循 Material Design 设计规范
Material UI 和 Joy UI 有很多相同相似的组件用法,学习完一个后很容易上手另外一个。
由于本文是学习 Material UI 的,所以在本文中无特殊说明的情况下 MUI
是指 Material UI。
特别补充:本文所学习的 Material UI 是指运行在前端 React 框架中的组件,不包含运行在后端的 React Server 组件。
下面提到的 MUI 都是指 Material UI。
默认安装 MUI
yarn add @mui/material @emotion/react @emotion/styled
更改默认样式引擎
MUI 默认样式引擎为 Emotion,如果你不想使用它,想改成 styled-components,则执行下面的安装
yarn add @mui/material @mui/styled-engine-sc styled-components
- Emotion 是一个转为 JS 编写控制 CSS 样式而设计的库,官网地址:https://emotion.sh/
- styled-components 也是一个在 JS 中写 CSS 样式的库,官网地址:https://styled-components.com/
- 上面提到的 样式引擎 实际上就是
CSS in JS
这个概念
安装相关字体
yarn add @fontsource/roboto
入口文件引入相关样式:
import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
安装图标
yarn add @mui/icons-material
你可能懵了,什么乱七八糟的,怎么要安装这么多东西?
这些到底要安装哪些? 都要安装吗?
别着急,我们慢慢学习。
假定我们只安装了默认的 MUI 组件:
yarn add @mui/material @emotion/react @emotion/styled
最基础的按钮示例:
import { Button } from '@mui/material'
const MyComponent = () =>{
const handleClick = () => { 。。。 }
return (
<div>
<Button
variant='contained'
style={{ 。。。 }}
onClick={handleClick}
>我是按钮</Button>
</div>
)
}
运行之后我们会看到一个 蓝色底的按钮
单看这个组件,我们会发现它的用法几乎和 Antd 没有什么区别。
按钮的 外观类型:
- Antd 的 Button 组件中我们是用 type 属性来设置按钮的外观:
- primary:主按钮
- 缺省:边框型(仅有边框,没有填充背景色),默认值
- dashed:虚线按钮
- text:文本按钮(文本颜色为普通黑白色)
- link:链接按钮(文本颜色为蓝色)
- MUI 的 Button 组件中则使用 variant 属性值来规定按钮外观:
- contained:有蓝色背景
- outlined:边框型(仅有边框,没有填充背景色)
- text:文本型(文本颜色为蓝色),默认值
- string:字符串文本型(文本颜色为普通黑白色)
如何将按钮设置为红色危险样式?
在 Antd 中的 Button 可以添加 danger
属性,让其变成红色具有危险警告性质的按钮样式外观。
相同的效果在 MUI 的 Button 中则是通过 color
属性来实现的。
<Button variant="contained" color="error">Hello World</Button>
查阅 Button 组件的 API 文档:
https://mui.com/material-ui/api/button/
我们可以看到 color
属性值的可设置属性为:
- inherit:随父级(可能是灰色)
- prinary:蓝色(默认值)
- secondary:紫色(辅助)
- success:绿色(成功)
- error:红色(错误)
- info:浅蓝(信息)
- warning:黄色(警告)
如何自定义颜色风格?
比如说我希望按钮的 success 不是默认的绿色,而是别的颜色。
可以通过 MUI 提供的自定义样式模板来统一修改。
-
先自定义一个 theme 样式模板
theme.tsx
import { createTheme } from '@mui/material/styles'; import { red } from '@mui/material/colors'; // A custom theme for this app const theme = createTheme({ palette: { primary: { main: '#556cd6', }, secondary: { main: '#19857b', }, error: { main: red.A400, }, }, }); export default theme;
-
使用
<ThemeProvider theme={theme}></ThemeProvider>
组件包裹住需要更改默认组件样式的组件main.tst (下面是伪代码)
import { ThemeProvider } from '@emotion/react'; import theme from './theme'; <ThemeProvider theme={theme}> <App /> </ThemeProvider>
按钮如何添加图标?
我们先说一下 Antd 中是如何使用图标的,一般来说分为 2 种:
-
从 Antd 官方的图标组件中引入并使用
yarn add @ant-design/icons
import { FireOutlined } from '@ant-design/icons' <FireOutlined />
-
从 iconfont.cn 上下载图标字体,引入以字体形式使用
MUI 使用图标也有 2 种方式。
第1种:使用官方图标库:
安装使用官方提供的图标库,这一种方式和 Antd 的完全相似
yarn add @mui/icons-material
import { AccountCircle } from '@mui/icons-material'
<AccountCircle />
硬要说区别的话,那就是 MUI 这套图标库的图标数量庞大,目前大约提供了 2100+ 个图标。
当你需要哪个图标时可以通过:https://mui.com/material-ui/material-icons/ 在线查找
用法示例:
import { Button } from '@mui/material'
import { AccountCircle } from '@mui/icons-material'
<Button
variant='contained'
startIcon={<AccountCircle />}
>Hello</Button>
startIcon 是指图标在前,文字在后,也可以使用另外一个属性 endIcon 即 文字在前,图标在后
关于图标 5 种类型的说明:
- Filled:实心填充
- Outlined:轮廓,非实心填充
- Rounded:边角处进行了圆滑处理
- Sharp:边角处比较尖锐处理,不圆滑
- Two Tone:挖空处不再是透明白色,而是浅灰色
第2种:自定义 SVG 图标组件
这种方式和 Antd 不同,Antd 是自定义字体文件。
假定我们现在想自定义一个名为 LightBulbIcon 的图标,那么方式为:
import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
function LightBulbIcon(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6C7.8 12.16 7 10.63 7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z" />
</SvgIcon>
);
}
使用 LightBulbIcon 图标:
<LightBulbIcon />
自定义 SVG 图标的用法和 MUI 官方图标一样。
SVG图标的一些属性设置
像上面我们自定义的 SVG 图标属于 SvgIcon,它默认支持的 SvgIconProps 具体文档可查阅:
https://mui.com/material-ui/api/svg-icon/
对我们来说比较常用的属性有:
-
fontSize(字体大小):'inherit' | 'large' | 'medium' | 'small' | string
默认值为 'medium'
-
sx(自定义样式):Array<func | object | bool> | func | object
例如我们可以写成这样:
<LightBulbIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
-
mr:1
这里的 mr 实际是 "marginRight" 的缩写请注意:这里的 1 并不是指 1px,而是有其特殊的含义,具体 1 的指是多少像素是由样式模板中的 spacing 决定的,假定模板中定义 spacing 的值为 8px,那么此时 mr:1 中的 1 即 8px,同理 mr:0.5 中的 0.5 即 (0.5*8px) = 4px
-
verticalAlign: 'middle'
:垂直对齐方式 -
关于 sx 中的样式语法以及其他缩写可查阅:https://mui.com/system/getting-started/the-sx-prop/
-
如何旋转、翻转图标?
首先 MUI 提供的图标本身完全遵循 CSS 元素,因此我们可以通过向其添加 CSS 中的 transform
样式来实现旋转或翻转图标。
//旋转 180度
<SpaceDashboard sx={{ transform: 'rotate(180deg)' }} />
//沿 Y 轴翻转
<ViewComfy sx={{ transform: 'scaleY(-1)' }} />
在 MUI 组件中推荐使用 sx={{ ... }} 属性来设置样式,而不是 style={{ ... }}
如何获取纯图标圆形按钮?
在 Antd 中设置 <Button shape="circle">
就可以获取圆形的纯图标按钮,在 MUI 中则采用的是 <IconButton>
组件来实现:
<IconButton aria-label="delete">
<DeleteIcon />
</IconButton>
注意不再是 而是
从上面这个最简单的按钮示例,我们已经对 MUI 有了初步的一些基础用法实践。
可以总结以下几点。
小总结1:关于安装 NPM 包
最基础核心的(必须):
yarn add @mui/material @emotion/react @emotion/styled
- @mui/material:MUI 组件库
- @emotion/react、@emotion/styled:Emotion 针对 react 的 JS 样式控制引擎(CSS in JS)
官方图标库(非必须):
yarn add @mui/icons-material
假设项目中根本不使用任何官方图标,如果使用图标也都是自定义 SVG 图标,那么自然也无需安装官方图标库
默认 roboto 字体(非必须):
yarn add @fontsource/roboto
假设你并不追求不同设备上都使用 MUI 默认的 Roboto 字体,那自然也无需安装引入 Roboto 字体
更改 MUI 中 JS 控制 CSS 样式引擎(不需要):
yarn add @mui/material @mui/styled-engine-sc styled-components
对,没错,MUI 默认的 CSS 引擎 Emotion 足够我们使用了,如非特殊情况完全没有必要将样式引擎更改为 styled-components,因此这个安装是不需要的。
小总结2:关于样式
全局样式:
- MUI 组件使用时,不需要引入全局样式,仅直接引入组件即包含样式
- 如果你想自定义组件样式风格,可以通过
<ThemeProvider theme={theme}>
方式来实现
图标样式:
-
对于 MUI 官方图标 和 自定义 SVG 图标,他们都支持一些基础的外观样式属性,例如 fontSize
-
同时支持 sx={{ ... }} 这种自定义样式
注意 sx={{ ... }} 中的 样式写法并不是传统的 style={{ ... }} 中的写法,而是 MUI 官方自定义的一种内置 CSS 语法,具体可查看:
特别说明:关于单个组件的样式——CSS in JS
尽管 MUI 的组件和 Antd 组件相似,也支持 className、style
这些属性,但是请注意 MUI 组件更推荐的做法是采用 CSS in JS
这种方法去定义组件样式。
MUI 的每一个组件都有 sx={{ ... }} 这个属性,强烈推荐使用此方式来修改当前组件样式。
非常不推荐使用 clasName、style、.scss 这种方式来定义 MUI 组件。
可能你习惯于使用 import ./index.scss + className
这种方式,但是既然使用 MUI,就可以尝试 CSS in JS
(sx={{ ... }}) 这种方式,当你习惯后会觉得非常方便,再也不用考虑样式命名这个事情了。
上面我们已经通过 <Button>
对 MUI 有了初步了解,那么接下来就过一遍 Material UI 文档和各个组件。
官方文档:https://mui.com/material-ui/getting-started/
官方文档左侧菜单面板说明:
Getting started
:快速开始,包含常见问题Components
:全部的组件用法示例Components API
:各个组件对应的 APICustomization
:自定义相关,例如 暗色模式、自定义样式模板 等How-to guide
:一些常见解决方案Experimental APS
:实验性质的 APIDiscover more
:探索更多Migration
:版本迁移相关Templates
:跳转至 社区模板站点
组件速览:
-
Inputs 输入相关组件:
-
Autocomplete:可自动填充的值的 input 输入框,涉及组件有
<Autocomplete>、<Popper>、<TextField>
-
Button:按钮,涉及组件有
<Button>、<ButtonBase>、<IconButton>、<LoadingButton>
-
Button Group:一组按钮,涉及组件有
<ButtonGroup>
-
Checkbox:多选框
-
Floating Action Button:浮动按钮,使用场景例如 网页右侧的 "返回顶部",涉及组件
<Fab>
-
Radio Group:单选框
-
Rating:等级评价组件,使用场景例如 给评价打几颗星,涉及组件
<Rating>
-
Select:下拉选择框,涉及组件
<Select>、<NativeSelect>
<NativeSelect>
组件是指同时有多项下拉选项可见的那种组件 -
Silder:滚动滑条,涉及组件
<Slider>
-
Switch:开关组件,涉及组件
<Switch> ...
-
Text Field:文本输入框
-
Transfer List:左右两侧列表数据互相转移,涉及组件
<List>、<ListItem>
-
Toggle Button:按钮组成的系列单选多选菜单选项,涉及组件
<ToggleButton>、<ToggleButtonGroup>
-
-
Data Display 数据展示组件:
- Avatar:头像组件
- Badge:提醒徽章
- Chip:紧凑元素,使用场景例如 多个标签展示
- Divider:分割线
- Icons:图标,若想使用前提是已安装
@mui/icons-material
图标库 - Material Icons 图标库图标展示页:就是展示出
@mui/icons-material
中的图标 - List 列表:各式各样的列表
- Table 表格:表格列表
- Tooltip:提示信息框,例如鼠标放到某个按钮上展示的 提示信息框
- Typography 排版文字:各种标题或显示文字
-
Feedback 反馈组件:
- Alert 提示框:各种状态的提示框
- Backdrop 半透明遮罩:覆盖页面内容的黑色半透明的遮罩,可用于自定义加载过程中的进度提示
- Dialog 半透明任务询问选择框:功能不太好描述,但是看一眼实际示例就知道是什么了
- Progress 进度状态:有转圈的,有显示百分比的
- Skeleton 骨架屏:骨架屏动画
- Snackbar 吐司工具:屏幕上临时显示一些提示信息,类似于 Antd 的 message
-
Surfaces 一些展示区域组件:
- Accordion 手风琴折叠:手风琴折叠面板
- App Bar 移动版顶部导航:包含菜单、当前标题 等
- Card 卡片:卡片面板
- Paper 纸张:移动端类似展示多个页面缩略图效果的组件
-
Navigation 导航组件:
- Bottom Navigation:APP 底部按钮导航
- Breadcrumbs 面包屑导航
- Drawer 侧边栏菜单面板:平时隐藏不可见,当需要时从侧边滑出显示
- Link 链接:相当于
<a>
标签 - Menu 菜单:各式各样的菜单,例如 三个点 的、点击显示选项的 等
- Pagination 分页:分页组件
- Speed Dial 一列动效菜单:默认就一个圆圈按钮,鼠标放上去带有动效的展示处一列子菜单
- Stepper 步骤组件:展示当前进行到第几个步骤的那种组件
- Tabs 标签切换组件
-
Layout 布局组件:
- Box 盒子:类似于 div
- Container 容器:类似于 div
- Grid:就是 Grid 布局组件
- Grid v2:新版 Grid 组件
- Stack 列组件:就是 Flex 布局组件,把子项排成一列
- Image List 图片列表:类似于图片瀑布流
- Hidden:该组件在 MUI v5 中已废弃
-
Utils 工具类型组件:
- Click-Away Listener:检测点击事件发生在子项之外
- CSS Baseline:控制 CSS 基线相关
- Modal:对话弹窗,涉及组件有
<Dialog>、<Drawer>、<Menu>、<Popover>
- No SSR:该组件文档已被移动到 https://mui.com/base-ui/react-no-ssr/
- Popover:鼠标点击按钮旁边的小弹出内容
- Popper:和 Popover 类似
- Portal:允许将子项内容渲染到组件之外其他 DOM 元素中
- Textarea Autosize:可根据内容自动调整尺寸的 Textarea
- Transitions:实际就是对 CSS 样式中的 transitions 封装
- useMediaQuery:监听 CSS 样式中的媒体查询,以是否显示某些组件
-
MUI X :MUI 的下一版本中的组件
- 这里的 X 就是指 下一版本,目前而言相当于 MUI v6
- 这些组件还处于不稳定中,因此不在本次学习中
-
Lab:实验性质的一些想法和组件
- 也不在本次学习中
好了,以上就是 MUI 包含的组件大体内容。
当你需要使用到哪个组件时自己去对应官方文档上查看示例和 API 就好。
思考题:对于 Layout 布局类的组件,我可以不可以不用?
例如:Box、Container、Grid 等这些布局容器类的组件,我不用它们,而是自己直接使用 div 行不行?
答:可以,但有弊端。
弊端就是:你的前端项目有可能需要 明暗模式(light/drak) 切换,这个时候你自己写的 div 可能就无法做出响应了
或者说你去要自己去处理明暗模式的响应,而上面这些布局组件则可以通过设置 <Xxx sx={{backgroundColor: 'primary.contrastText}} />
很轻松来实现切换背景颜色。
primary.contrastText 是主题样式中的一个变量配置项。
我们这里只是举例使用了 primary.contrastText,还可以设置成主题样式中的其他颜色变量。
同理,对于一些文本元素,也牵扯到 亮/暗 模式切换后文字颜色变化,所以推荐使用 <Typography>
组件,而不是自己去写 <span>
。
const themeOptions: ThemeOptions = {
palette: {
mode: 'dark'
},
components: {
MuiTypography: {
defaultProps: {
color: 'textPrimary'
}
}
}
}
<Typography>footer</Typography>
在上面代码中我们将 文本组件 文字颜色设置为主题中的 'textPrimary'。
对于新手而言需要大量的实际操作才能够逐渐熟悉 主题 中的各个颜色变量名称。
补充:聊一聊那些相似的 "弹窗、弹层" 组件
在 MUI 中有几个 "弹窗、弹层" 组件无论从名称还是用法来说都比较相似。
它们是:
-
Dialog:对话窗,它强调的是 "询问用户下一步该如何继续?"
使用场景例如:
- 询问用户是否同意 某某 条款
- 让用户输入一个某个值,例如输入一个邮箱地址,以便继续下一步
-
Modal:模态框,没错 Modal 准确的翻译就是 "模态框",尽管我们日常习惯称呼它为 "对话框"
你可以使用 Modal 自己组建很复杂的 "弹窗、对话框"
-
Popover:弹出窗口,不包含半透明背景层,但依然会阻止用户操作其他元素
使用场景:
- 展示某些待用户选择的隐藏选项或菜单
-
Popper:弹出层
使用场景:
- 显示用户刚才操作有关的弹出层内容,但不阻止用户去做其他事情,例如 "点击按钮显示某个链接地址,可以让用户去复制这个链接地址文字"
-
Backdrop:弹出的半透明覆盖层,至于中间核心区域的内容则是由用户自己添加组件决定
使用场景:
- 使用 Backdrop 作为背景,中间内容可以是一个进度条组件
区别1:阻止用户操作其他元素、阻止用户滚动页面
- Dialog、Modal、Popover 这 3 个组件都会阻止
- Popper 都不会阻止
- Backdrop:会阻止用户操作其他元素,但不阻止用户滚动页面
区别2:出现的位置
- Dialog、Modal 都显示在网页中间位置
- Popover、Popper 显示在操作的元素位置旁边
- Backdrop:覆盖整个网页
你需要做的事情就是根据每个组件的不同特征,结合自己的实际场景需求,来选择使用哪个组件。
在上面初探 MUI 时我们只是简单提了一下使用 <ThemeProvider theme={theme}>
修改按钮不同状态下的颜色,下面我们将详细讲解如何自定义主题样式。
theme 这个单词的翻译就是 主题
-
项目根目录创建
theme.tsx
src/theme.tsx
import { createTheme } from "@mui/material"; const theme = createTheme({ }) export default theme
尽管我们知道它就是返回一个对象而已,但MUI 官方示例写的就是 .tsx 而不是 .ts
-
在整个项目的顶级组件 main.tsx 中使用
<ThemeProvider>
包裹住<App>
src/main.tsx
import { ThemeProvider } from '@mui/material' import ReactDOM from 'react-dom/client' import theme from './theme.tsx' import App from './App.tsx' import React from 'react' import './index.scss' ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <ThemeProvider theme={theme}> <App /> </ThemeProvider> </React.StrictMode> )
好了,只此一个最基础,实际啥也没做的 自定义主题样式 已经完成了。
那么接下来我们就要开始去深入学习了解自定义主题,并完善 theme.tsx。
关于<ThemeProvider>
的嵌套说明:
在上面示例中我们使用了一个 <ThemeProvider>
包裹住了 <App>
组件,那么就意味着整个项目中的组件都将受到影响。
但 <ThemeProvider>
是允许多层嵌套的,和 CSS 一样,也是按照最近原则生效。
<ThemeProvider theme={outerTheme}>
<Checkbox defaultChecked />
<ThemeProvider theme={innerTheme}>
<Checkbox defaultChecked />
</ThemeProvider>
</ThemeProvider>
MUI 默认主题中定义了一些 CSS 相关的变量配置项,我们可以通过修改这些变量配置项来整体修改主题样式。
最常用的变量配置:
-
.palette:调色板(主题色)配置
-
.typography:版式配置
-
.spacing:单位间距
默认值为 8,即 8px
-
.breakpoints:断点(媒体查询)
这些断点对应有简写,默认值如下:
- xs:0px
- sm:600px
- md:900px
- lg:1200px
- xl:1536px
-
.zIndex:z-index 属性配置
分别定义不同组件(例如 fab、modal 等) 他们的 z-index 的值
-
.transitions:过渡动画配置
-
.components:组件配置
注意对于 MUI 的各个组件都需要增加一个
Mui
的前缀,假设我们想统一配置<Container>
组件,那么在此处应该写成MuiContainer
:const themeOptions: ThemeOptions = { components: { MuiContainer: { defaultProps: { disableGutters: true, maxWidth: false }, styleOverrides:{ ... } } } }
上面配置中:
disableGutters: true
意思是:禁止(取消) Container 两侧的 pading 空隙,默认 Container 两侧的空隙是 24pxmaxWidth: false
意思是:取消最大宽度限制,默认 maxWidth 为 1536px,设置为 false 后意味着相当于设置宽度为 100%关于每个组件的更多配置项,可自行查阅文档,这里就不逐一举例了。
palette:调色板(主题色)配置
.palette
是我们最常用、最复杂的主题变量,从它的 .d.ts 可以看出 .palette
有非常多的子配置项。
export interface PaletteOptions {
primary?: PaletteColorOptions;
secondary?: PaletteColorOptions;
error?: PaletteColorOptions;
warning?: PaletteColorOptions;
info?: PaletteColorOptions;
success?: PaletteColorOptions;
mode?: PaletteMode;
tonalOffset?: PaletteTonalOffset;
contrastThreshold?: number;
common?: Partial<CommonColors>;
grey?: ColorPartial;
text?: Partial<TypeText>;
divider?: string;
action?: Partial<TypeAction>;
background?: Partial<TypeBackground>;
getContrastText?: (background: string) => string;
}
.palette 配置项说明:
-
primary(主要)、secondary(次要)、error(错误)、warning(警告)、info(信息)、success(成功)
这几个都是和颜色有关的配置,他们需要配置以下 4 个属性
light?: string; //亮模式下的颜色 main: string; //主体颜色 dark?: string; //暗模式下的颜色 contrastText?:string; //当前模式下内容(对比)文字的颜色,例如背景色为蓝色,则蓝色背景上的文字颜色可以是白色
-
mode:当前处于哪个模式,默认为 "light"
PaletteMode = 'light' | 'dark'
-
tonalOffset:色调偏移,仅对自定义主题颜色生效,对默认主题颜色不生效
export type PaletteTonalOffset = number | { light: number; dark: number; };
tonalOffset 的值是一个 0 ~ 1 的数字,它是指主题颜色从 亮(light) 到 主色(main) 再到 暗(drak) 的亮度过渡比例。该值可以是一个数值,也可以分别设置不同模式对应的数值。
假定该值为 0.2,表达的含义是:
- 亮模式的颜色亮度相对主颜色亮度 偏差(增加) 0.2(较亮较浅)
- 暗模式的颜色两对相对主颜色亮度 偏差(减少) 0.2(较暗较深)
假定亮度足够高时,最终呈现的是 白色
假定亮度足够低时,最终呈现的是 黑色
-
contrastThreshold:对比度阈值,用来表示 背景色与文字的对比度阈值(强烈程度),默认值为 3
contrastThreshold?: number;
你想象一下:一个警告按钮,背景色是橙黄色,按钮上的文字应该什么颜色?
默认值为 3 即 文字为白色,如果改成 4.5 则文字变成偏暗色
假定你要修改 contrastThreshold 的值,一定要考虑到 色弱 的人,如果背景色和文字颜色对比度不够明显,可能这部分人就不容易看清楚按钮上的文字。
-
common:定义所谓的 黑与白 的颜色值
export interface CommonColors { black: string; white: string; } common?: Partial<CommonColors>;
-
grey:灰色,这里实际指某个颜色中不同灰度( 从 50 ~ 900 )的颜色
你把它想象成 字体粗细 中的 50 ~ 900 那个概念,即用数字来表达 灰 的程度
-
text:
export interface TypeText { primary: string; secondary: string; disabled: string; } text?: Partial<TypeText>;
-
divider:
divider?: string;
-
action:
export interface TypeAction { active: string; hover: string; hoverOpacity: number; selected: string; selectedOpacity: number; disabled: string; disabledOpacity: number; disabledBackground: string; focus: string; focusOpacity: number; activatedOpacity: number; } action?: Partial<TypeAction>;
-
background:
export interface TypeBackground { default: string; paper: string; } background?: Partial<TypeBackground>;
-
getContrastText:
getContrastText?: (background: string) => string;
后面几个属性暂时没用到,所以就先空着,以后用到知道是什么了再来完善。
上面讲的仅仅是 .palette
,剩下的其他配置项,每个也都有非常多的子配置项,具体太多就不逐一说明了,可以自己查阅文档。
自定义变量:
上面提到的 palette、spacing 等都是 MUI 定义好的变量配置项,如果你想增加自定义的变量配置项也是可以的,当前前提是你真的需要。
这里举一个例子来说明如何添加自定义变量。
假定我们要添加一个名为 puxiao
的变量配置项,那么我们需要做下面 2 件事情:
-
向 Theme、ThemeOptions 中增加关于 puxiao 的类型定义
declare module '@mui/material/styles' { interface Theme { puxiao: { xx: string; }; } // allow configuration using `createTheme` interface ThemeOptions { puxiao?: { xx?: string; }; } }
请注意:
- puxiao 在 Theme 中为必填属性
- puxiao 在 ThemeOptions 中为非必填属性
-
然后就可在 createTheme() 中添加 puxiao 变量配置项了
createTheme({ puxiao:{ xx: ... } })
特别说明:我暂时也没遇到过这种需要增加自定义变量的场景需求,那实际中会遇到什么问题暂时还不清楚。
注意:vars
这个变量是 MUI 内部已使用的变量,你自定义变量中不允许使用该字段。
关于主题配置项的各个详细用法,去查阅官方文档即可。
线上可视化主题配置:
MUI 还为我们提供了一个在线可视化配置主题的网站:
https://zenoo.github.io/mui-theme-creator/
你可以直接在这个网站上配置自定义主题,然后将配置结果复制到自己的项目中使用。
获取当前主题配置:useTheme()
MUI 提供了一个 hook 函数用来获取当前主题配置:useTheme()
import { useTheme } from '@mui/material'
const theme = useTheme()
console.log(theme.palette.mode)
特别注意:此方法仅为获取主题,但不能直接修改主题
(你直接修改 ttheme.palette.mode 的值并不会触发主题模式生效变更)
假定我们已经在 palette
中对 亮暗 模式中的颜色进行了设置。
默认MUI 采用的是 亮(light)模式,想切换到暗模式修改配置:
const theme = createTheme({
palette: {
mode: 'dark'
},
})
但是上面存在问题:我们希望明暗模式是可以用户通过按钮切换,而不是写死到 palette 中。
那么我们接下来将改造一下代码。
实现 点击按钮进行 亮/暗模式 切换 的实现思路:
- 先定义好 主题配置项
- 使用 zustand 做来状态管理,创建一个名为 useThemeData 的状态管理对象,其内部包含 模式(mode) + 主题(theme)
- 使用
<IconButton>
来创建可切换的 模式图标按钮 - 修改
<ThemeProvider>
组件的位置,由 main.tsx 改到 App.tsx,以便于监听 useThemeData 的变换并重新触发<App>
重新渲染
具体代码:
src/common/theme-options.ts
import { ThemeOptions } from '@mui/material'
const themeOptions: ThemeOptions = {
palette: {
mode: 'light',
}
}
export default themeOptions
在上面配置项中,我们只是为了演示效果,仅仅配置了 platte.mode 的值,实际中应该把自定义主题的其他配置项也都添加上。
src/store/useThemeData.ts
import { PaletteMode, Theme, createTheme } from '@mui/material'
import { deepmerge } from '@mui/utils'
import { create } from 'zustand'
import { themeOptions } from '@/common'
interface UseThemeData {
mode: PaletteMode
theme: Theme
toggleMode: () => void
setData: (newMode: PaletteMode) => void
}
const useThemeData = create<UseThemeData>()((set, get) => ({
mode: themeOptions.palette?.mode || 'light',
theme: createTheme(themeOptions),
toggleMode: () => {
const newMode = get().mode === 'light' ? 'dark' : 'light'
get().setData(newMode)
},
setData: (newMode) => set({
mode: newMode,
theme: createTheme(deepmerge(themeOptions, { palette: { mode: newMode } }))
})
}))
export default useThemeData
关于 useThemeData.ts 的说明:
- 首先引入之前定义好的 主题配置项
- 然后通过 zustand 创建了一个数据状态,其包含:
- mode:当前处于哪种模式(light/dark)
- theme:主题实例
- toggleMode:切换当前模式的调用函数
- setData:根据 newMode 来更新 mode 和 theme
- 关于 setData 补充 2 点:
- 我们使用了 MUI 为我们提供的一个 deepmerge 函数,用来深度合并 2 个对象
- 这 2 个对象分别是:
- 我们之前定义好的 主题配置项
- 仅包含当前 处于哪个模式的 配置项
- 最终合并之后就得到了切换过模式后的主题配置项,然后通过 createTheme() 重新生成得到一份 主题对象
如果不使用 deepmerge 函数,我们可以通过 JS 本身的解构方式来实现合并,代码为:
theme: createTheme({ ...themeOptions, palette: { ...themeOptions.palette, mode: newMode } })
在我们的计划里,最终:
- mode、toggleMode() 给 负责点击切换模式的 图标按钮 使用
- theme 给
<ThemeProvider>
使用
那么接下来编写 模式图标切换按钮(<ThemeModeSet>
) 和 App.tsx。
src/components/theme-mode-set/index.tsx
import { useThemeData } from "@/store"
import { Brightness4, Brightness7 } from "@mui/icons-material"
import { IconButton } from "@mui/material"
const ThemeModeSet = () => {
const { mode, toggleMode } = useThemeData(state => ({
mode: state.mode,
toggleMode: state.toggleMode
}))
return (
<IconButton onClick={toggleMode}>
{mode === 'light' ? <Brightness4 /> : < Brightness7 />}
</IconButton>
)
}
export default ThemeModeSet
App.tsx,下面是伪代码
import { ThemeProvider } from '@mui/material'
import { useThemeData } from './store'
import { ThemeModeSet } from '@/components'
function App() {
const theme = useThemeData(state => state.theme)
return (
<ThemeProvider theme={theme}>
<ThemeModeSet />
{ ... }
</ThemeProvider>
)
}
export default App
至此,亮/暗 模式切换效果完成。
虽然我们上面讲的是如何动态修改 亮/暗 模式,但是按照这个思路(套路) 我们可以修改任何 主题样式 中的配置项,甚至是一个全新的主题。
目前为止,我们已经掌握了 Material UI 的基础知识:
- Material UI 简介,与 Antd 的区别
- Material 安装、组件基础用法、自定义组件样式
- Material 自定义主题,亮/暗 模式切换
那么接下来就可以在实际项目中使用 Material UI 了。
由于 Material UI 组件特别强调 "组合与自定义",所以实践过程中还会遇到一些问题。
再来慢慢更新本文。