Material UI 2019 年 4 月更新解读:TypeScript 演示化、Hooks 迁移与全局 class 策略的技术回溯
2026/9/7 9:30:44 网站建设 项目流程

Material UI 2019 年 4 月更新解读:TypeScript 演示化、Hooks 迁移与全局 class 策略的技术回溯

【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Google's Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui

本篇文章以仓库内历史开发月报 docs/pages/blog/april-2019-update.md 为核心线索,逐条回溯 Material UI(即今天的@mui/material)在 2019 年 4 月交付的工程化变革,并结合当前仓库源码,验证这些能力(TypeScript 双版本演示、Hooks 化 API、responsiveFontSizes()useScrollTrigger()滚动行为、全局 class 命名哲学等)如何在今天的代码库中落地与演化。读者将借此理解一套组件库在版本发布前夜的工程沉淀方式,并可直接套用其中的responsiveFontSizes()配置与 App Bar 滚动模式到自己的项目里。

2019 年 5 月发布的这篇月报,恰好处于 Material UI v3 与 v4 两个大版本之间——彼时团队刚完成大量内部重构,正准备在 React Europe 大会上发布 v4 稳定版。虽然时隔数年,月报里提到的多数改动点,今天仍能在仓库源码中找到它们的"后代实现",非常适合作为研究组件库演进史的切片。

一、演示代码全面 TypeScript 化:一份代码,两种语言

月报第一项重大改动是:把绝大多数官方 demo 迁移到 TypeScript,由@eps1lon牵头,并得到@merceyz、@sperry94、@jasondashwang、@bh1505、@donigianrp、@Dudrie、@eluchsinger、@cahilfoley、@gabrielgene、@kenzhemir、@Adherentman、@lksilva、@Tevinthuku等十余位贡献者的协助。迁移完成后,文档站的每个示例都带有一个 JS/TS 切换按钮:

这个习惯在今天的仓库中依然可见:绝大多数 demo 都以.js.tsx成对存在。以 Transfer List 为例,TransferList.js 与 TransferList.tsx、SelectAllTransferList.js 与 SelectAllTransferList.tsx 并存,二者的代码可随时互译对照。配套的 formattedTSDemos.mjs 脚本承担了这些 TS 演示的格式化工作。

月报特别强调了一个衍生收益:

支持 TypeScript 演示有一个重要含义——它倒逼我们提供可用的 TypeScript 类型定义。

也就是说,示例只要能被 TypeScript 编译器接受,类型声明(.d.ts)就必须真实可用,避免出现"文档写着能用、类型却报错"的割裂。在今天,Material UI 的类型定义已经覆盖到 mui-material 的全部公开 API,并配有tsconfig.json与严格的类型校验流程(参见 validateTypescriptDeclarations.mts)。从源码结构看,这套"示例反哺类型"的机制,正是从这次迁移开始建立的。

二、从 Class 组件到 Hooks:一次影响深远的内部重构

月报披露的第二项工程是:将大批组件从 class 写法迁移到 Hooks,由@joshwooding牵头。当时月报把"为什么这样做"的详细解释留给了即将发布的 v4 发布博文,理由集中于 Hooks 带来的逻辑复用更小的包体积

这项重构对开发者最直观的成果,是出现了大量可直接被业务复用的公开 Hooks API。一个至今仍在文档与源码中活跃的例证是useScrollTrigger()——它被实现在 packages/mui-material/src/useScrollTrigger/useScrollTrigger.js 中:

'use client'; import * as React from 'react'; function defaultTrigger(store, options) { const { disableHysteresis = false, threshold = 100, target } = options; const previous = store.current; if (target) { // Get vertical scroll store.current = target.pageYOffset !== undefined ? target.pageYOffset : target.scrollTop; } if (!disableHysteresis && previous !== undefined) { if (store.current < previous) { return false; } } return store.current > threshold; }

这个只有约 50 行的 Hooks 实现,今天依然沿用着 Hooks 时代确立的模式:用useRef保存上一次滚动位置、用useState持有触发态、在useEffect中为target注册passive: true的 scroll 监听器(见 useScrollTrigger.js)。对应的行为断言可见于 useScrollTrigger.test.js。

三、与 Material Design 指南的进一步对齐

月报提到,团队对Snackbar、List、Checkbox、Radio 与 Switch进行了视觉更新,使其更贴合 Material Design 规范。这类改动通常不改变组件 API,却直接影响大量存量应用的观感,属于"悄悄变好看"的破坏性较小的发布内容。

一个可以佐证"对齐规范"是持续工程的证据是:文档中至今保留着 enableColorOnDark 这类"严格遵循规范、但允许主动越界"的逃生舱设计——Material Design 指南规定深色模式下 App Bar 的color属性不生效,而enableColorOnDarkprop 允许开发者显式覆盖这一默认行为。可见"对齐指南"与"允许开发者偏离指南"始终是并行的两条设计原则。

四、新增 Transfer List 演示:组合而非封装

4 月新增了一个Transfer List(穿梭框)组件的构建演示,用于把一组列表项在"未选/已选"两个列表间来回移动:

这个功能至今仍是官方文档的一部分,见 transfer-list.md。该组件文档的components字段点明了它的本质——它不是一个导出的高级组件,而是ListListItemCheckboxSwitch四种基础组件的组合示范:

components: List, ListItem, Checkbox, Switch

文档明确列出了两类已知限制,理解这些限制能帮你判断何时该自己组装穿梭框:

  • 只适配桌面端。若可选项数量有限,官方建议改用 Autocomplete 的多值模式(当前仓库中对应/material-ui/react-autocomplete路由);若必须支持移动端,可跟踪上游 issue #27579。
  • npm 上没有可导入的高级组件,demo 全部基于基础组件组合实现。

也就是说,Transfer List 的定位是一份"组合配方"而非"开箱组件"。今天类似的组合思路也遍布文档:例如官方RatingSliderTimeline等页面均可在 docs/data/material/pages.ts 的组件清单中找到对应入口。

五、全局 class 命名:classesAPI 的反思与样式隔离责任

4 月还发生了一项对样式定制方式影响深远的改动:class 名称生成改为输出全局 class 名。月报透露了背后的用户痛点:很多人在classesAPI 上挣扎——它面向纯 CSS 与 styled-components 用户,但你常常难以确定自定义 class 应该施加到"哪一个内部元素"上,写起来也繁琐。

⚠️ 使用全局 class 名称带来更强能力的同时,也意味着更大的责任。我们鼓励任何能提升**自定义样式隔离(custom style isolation)**的模式。

这条建议至今仍是官方定制文档的主线。当前仓库把classesprop 与基于 class 的覆盖方式系统整理在 how-to-customize.md 中,包括:如何用全局 CSS 规则覆盖.Mui*前缀的组件 class、如何在样式对象里引用伪类(如&.Mui-disabled)、以及如何通过classes同时注入多个 class 等。其共同目标是:让"覆盖默认样式"这件事既有足够力量、又不互相污染

从演进视角看,4 月埋下的"全局命名"实验,最终在后续大版本中被沉淀为可预测、可静态分析的稳定 class 生成策略——这也是今天大量"零 runtime、纯 CSS"定制方案能够成立的前提。

六、日期/时间组件收归官方组织,最终走向 MUI X

月报记载,社区维护的material-ui-pickers正式迁入官方组织,改名为@material-ui/pickers,并特别感谢了该组件的作者@dmtrKovalenko的创建与长期维护。

此后这段历史的下一站,在仓库的博客时间线上有清晰记录:官方发布 lab-date-pickers-to-mui-x.md 与 lab-tree-view-to-mui-x.md,宣布 Date Picker、Tree View 等组件从@mui/lab移交到商业产品线MUI X独立维护与迭代。这是一个值得关注的行业模式:社区插件(pickers)→ 官方化(@material-ui/pickers)→ 独立产品线(MUI X)三步走,既保证了核心包体积与发布节奏不被重组件拖累,也给了复杂组件更专业的演进空间。

七、迈向 Concurrent React:Strict Mode 与键盘可达性

4 月的两项"打磨类"工作同样不可小觑:

  1. 清理 Strict Mode 警告,为后续 Concurrent React 支持铺路。Strict Mode 在开发期会刻意双重调用渲染、生命周期与副作用函数,把隐患暴露在早期;能在该模式下零警告运行,是面向未来并发渲染的一份"体检报告"。
  2. 显著改善 Select、Menu、Button、Tooltip 的键盘行为
    • 方向键切换即时响应("箭头键变化感觉是瞬时的");
    • Select 列表项支持按字母键快速定位选择
    • focus visible 状态检测更可靠(区分鼠标点击与键盘聚焦)。

"聚焦可见性"这个话题在后来的文档中延续为独立章节,当前仓库仍保留focus-visible相关的定制指南目录(见 docs/data/material/customization/focus-visible)。键盘可达性从来不是一次性功能,而是贯穿每个版本回归测试清单的长期指标。

八、responsiveFontSizes():主题级的响应式排版方案

4 月新增了响应式字体大小支持:用responsiveFontSizes()包裹主题即可让排版随断点缩放。官方宣传图中展示的正是不同视口下标题字号的变化:

该功能至今完整保留在当前源码 responsiveFontSizes.js 中。先给出最基础的用法:

import { createTheme, responsiveFontSizes } from '@mui/material/styles'; let theme = createTheme(); theme = responsiveFontSizes(theme);

(注:2019 年该 API 还归属@material-ui/core时代,今天在@mui/material中从@mui/material/styles导入。仓库仍在 docs 维护一份逐步拆解效果的自定义 demo,见 CustomResponsiveFontSizes.js 及配套的 typography.md 定制指南。)

配置项详解(与源码逐一对应)

从 responsiveFontSizes.js 的函数签名可见全部默认选项:

选项默认值含义
breakpoints['sm', 'md', 'lg']参与字号插值的主题断点(会换算成实际像素值);在最大断点处保留原设计字号
disableAlignfalse设为true则关闭 4px 基线网格对齐
factor2缩放系数。越小,最小字号与最大字号差距越大
variantsh1~overline共 13 个排版变体参与缩放的字体系列,可自行裁剪

核心算法(见 responsiveFontSizes.js)可以概括为四步:

  1. 统一换算为 rem:依据theme.typography.htmlFontSize(默认 16px,即浏览器默认值)把各变体的fontSize转成rem;仅字号大于 1rem 的变体才参与缩放,避免正文/注脚被无谓地放大缩小。

  2. 确定缩放区间maxFontSize = 当前 rem 字号minFontSize = 1 + (maxFontSize - 1) / factor。factor 默认 2,即把超过 1rem 的部分压缩一半。

  3. 网格对齐(默认开启):使用alignProperty配合fontGrid({ pixels: 4, lineHeight, ... })把字号对齐到 4px 基线网格,保证行与行之间节奏整齐。注意:开启对齐时要求lineHeight无单位值,否则会抛出如下错误(源码位置):

    MUI: Unsupported non-unitless line height with grid alignment. Use unitless line heights instead.
  4. 保留最大断点的原始字号:为了防止网格对齐把最大断点处的字号"吸"走,源码(第 83-91 行)会在最后一个断点处显式生成一条@media (min-width: …px)规则,把fontSize精确还原为设计稿的原始值(该修复源自上游 issue #40255)。

最终每个变体会被展开为一组带媒体查询的响应式对象写入theme.typography[variant]。行为级验证可以参考 responsiveFontSizes.test.js。

九、App Bar 自定义滚动行为:官方给出的三种成熟模式

4 月还支持了自定义页头(header)滚动行为。月报内嵌的演示视频保存在仓库中(scroll-trigger.mp4),而完整教程在 app-bar.md 的 Scrolling 小节,共给出三种官方推荐组合:

模式效果说明
Hide App Bar向下滚动隐藏 App Bar,向上滚动重新出现useScrollTrigger()+Slide,为阅读腾出空间
Elevate App Bar离开页面顶部时给 App Bar 加阴影传达"你已不在顶部"的层级信息
Back to top滚动后浮出回到顶部的 FAB长页面快速回到顶部的快捷通道

文档中给出的最小实现骨架(app-bar.md):

import useScrollTrigger from '@mui/material/useScrollTrigger'; function HideOnScroll(props) { const trigger = useScrollTrigger(); return ( <Slide in={!trigger}> <div>Hello</div> </Slide> ); }

useScrollTrigger([options])的参数语义

对照文档(app-bar.md)与源码(useScrollTrigger.js),三个选项的真实行为如下:

选项默认值真实语义(依据源码实现)
disableHysteresisfalse关闭"迟滞"。默认开启时,只要滚动方向向上(store.current < previous)就立刻返回false,避免在阈值附近来回抖动;关闭后则只看是否超过阈值
targetwindow监听滚动的容器节点;读取pageYOffset(window 场景)或scrollTop(容器场景)
threshold100垂直滚动量严格大于该值(exclusive)时trigger才为true

返回值为布尔trigger,指示"滚动位置是否满足条件"。由于defaultTrigger是可通过getTrigger注入的(见 useScrollTrigger.js),你甚至可以替换整个判定逻辑去响应任意自定义滚动条件。

十、月度数字:一次社区协作的密度样本

月报为 4 月的工作量给出了精确统计:

我们接受了来自69 位贡献者243 个提交,改动了1,545 个文件,其中+36,461 行新增、-20,237 行删除

在月报语境里,净增约 1.6 万行的同时还有约 2 万行的删除,说明该月相当一部分工作是"重写/重构"性质(TypeScript 化、Hooks 化)而非单纯堆功能——这正是我们上面看到的各项内部变革在数据层面的投影。

十一、2019 年 5 月路线图:哪些愿望后来成真了

月报在文末(以"我们会尽力,但不打包票"为前提)列出 5 月路线图:

  • React Europe 大会期间发布 Material UI v4 稳定版
  • 启动新一轮组件支持计划,候选清单包括:Layout、Combobox、Slider(及 range)、Dropdown、Tree view、Dropzone/Upload、Skeleton、Jumbotron、Carousel、Rating、Timeline。
  • 一件"大事"🌈。
  • 希望用户用 GitHub issue 上的 👍 投票来影响优先级排序。

把这份 2019 年的愿望清单与当前仓库对照,可以看到相当高的兑现率

  • SliderRatingSkeletonTimelineAutocomplete(承接 Combobox 场景)如今都是 Material UI 的正式文档组件,均能在 docs/data/material/pages.ts 的组件清单中查到对应入口(/material-ui/react-slider/material-ui/react-rating/material-ui/react-skeleton/material-ui/react-timeline/material-ui/react-autocomplete);
  • 定位更重的 Tree View 与日期选择器等组件,最终按照前文"第七节"所述的路径移交 MUI X 独立演进(见 lab-tree-view-to-mui-x.md);
  • Layout、Dropdown、Dropzone/Upload、Jumbotron、Carousel 这类"应用级组合"需求,则更多被引导到基础组件的自由组合或社区方案中去解决,未以高级组件形式进入核心包。

至于那件"大事"🌈,月报并未展开细节;从今天的时间点回看,它大概率指向紧接着到来的 v4 大版本及其后续生态动作——但在仓库证据范围内我们不做进一步猜测。

结语:一份月报里的工程方法论

回看这份 2019 年 4 月的月报,它真正值得借鉴的并非某一个具体功能,而是一套可持续的工程节奏:用 TypeScript 示例反向约束类型质量、用 Hooks 重构换取长期可维护性、用全局 class 策略重塑样式定制的边界、用官方 demo(如 Transfer List)替代重量级封装、用独立产品线(MUI X)承接高复杂度组件。这些决策中沉淀下来的responsiveFontSizes()useScrollTrigger(),今天依然是任何 MUI 项目都能直接上手的能力。如果你正在学习或维护 Material UI,这份月报连同仓库里它的"现役后代实现",是一份不可多得的活教材。

【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Google's Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询