1. 简介
Material Components (MDC) 有助于开发者实现 Material Design。MDC 是由一组 Google 工程师和用户体验设计人员倾心打造的,提供数十种精美实用的界面组件,可用于 Android、iOS、Web 和 Flutter.material.io/develop |
MDC Web 旨在集成到任何前端框架中,同时秉持 Material Design 原则。以下 Codelab 将引导您构建一个以 MDC Web 为基础的 React 组件。在此 Codelab 中学到的原则可应用于任何 JavaScript 框架。
MDC Web 的构建方式
MDC Web 的 JavaScript 层由每个组件的三个类组成:Component、Foundation 和 Adapter。此模式使 MDC Web 能够灵活地与前端框架集成。
基础包含实现 Material Design 的业务逻辑。基础不引用任何 HTML 元素。这样,我们就可以将 HTML 互动逻辑抽象到 Adapter 中。基础具有适配器。
适配器是一种接口。基础通过引用适配器接口来实现 Material Design 业务逻辑。您可以使用不同的框架(例如 Angular 或 React)来实现适配器。适配器的实现与 DOM 结构进行交互。
组件具有基础,其作用是
- 使用非框架 JavaScript 实现适配器,并
- 提供代理到 Foundation 中方法的公共方法。
MDC Web 提供的内容
MDC Web 中的每个软件包都包含组件、基础和适配器。如需实例化 Component,您必须将根 element 传递给 Component 的构造函数方法。组件实现了一个与 DOM 和 HTML 元素互动的适配器。然后,组件会实例化基础,后者会调用适配器方法。
如需将 MDC Web 集成到框架中,您需要使用该框架的语言/语法创建自己的组件。框架组件实现了 MDC Web 的适配器,并使用 MDC Web 的基础。
构建内容
此 Codelab 演示了如何构建自定义 Adapter,以使用 Foundation 逻辑来实现 Material Design React 组件。它涵盖了集成到框架中的高级主题。此 Codelab 使用 React 作为示例框架,但此方法可应用于任何其他框架。
在此 Codelab 中,您将构建顶部应用栏,并重新创建顶部应用栏演示页面。演示页面布局已设置完毕,您可以开始处理顶部应用栏。顶部应用栏将包含:
- 导航图标
- 待办项
- 有 4 种变体可供选择:短、始终处于收拢状态、固定和突出变体
所需条件:
- 较新版本的 Node.js(与 npm 捆绑在一起,npm 是一个 JavaScript 软件包管理器)
- 示例代码(将在下一步中下载)
- 已掌握 HTML、CSS、JavaScript 和 React 方面的基础知识
您如何评价自己在 Web 开发方面的经验水平?
2. 设置开发环境
下载起始 Codelab 应用
起始应用位于 material-components-web-codelabs-master/mdc-112/starter 目录中。
…或从 GitHub 克隆
如需从 GitHub 克隆此 Codelab,请运行以下命令:
git clone https://github.com/material-components/material-components-web-codelabs
cd material-components-web-codelabs/mdc-112/starter
安装项目依赖项
在起始目录 material-components-web-codelabs/mdc-112/starter 中,运行以下命令:
npm install
您会看到大量 activity,并且在最后,您的终端应该会显示已成功安装:

运行起始应用
在同一目录中,运行以下命令:
npm start
系统将启动 webpack-dev-server。让浏览器指向 http://localhost:8080/,以查看相应页面。

大功告成!浏览器中应该正在运行“顶部应用栏 React 演示”页面的起始代码。您应该会看到一堆 lorem ipsum 文本、一个控件框(右下角)和一个未完成的顶部应用栏:

查看代码和项目
如果您打开代码编辑器,项目目录应如下所示:

打开 App.js 文件,然后查看包含 <TopAppBar> 组件的 render 方法:
App.js
render() {
const {isFixed, isShort, isRtl, isProminent, isAlwaysCollapsed, shouldReinit} = this.state;
return (
<section
dir={isRtl ? 'rtl' : 'ltr'}
className='mdc-typography'>
{
shouldReinit ? null :
<TopAppBar
navIcon={this.renderNavIcon()}
short={isShort}
prominent={isProminent}
fixed={isFixed}
alwaysCollapsed={isAlwaysCollapsed}
title='Mountain View, CA'
actionItems={this.actionItems}
/>
}
<div className={classnames('mdc-top-app-bar--fixed-adjust', {
'mdc-top-app-bar--short-fixed-adjust': isShort || isAlwaysCollapsed,
'mdc-top-app-bar--prominent-fixed-adjust': isProminent,
})}>
{this.renderDemoParagraphs()}
</div>
{this.renderControls()}
</section>
);
}
这是应用中 TopAppBar 的入口点。
打开文件 TopAppBar.js,这是一个包含 render 方法的纯 React Component 类:
TopAppBar.js
import React from 'react';
export default class TopAppBar extends React.Component {
render() {
return (
<header>
TOP APP BAR
</header>
);
}
}
3. 组件的组成
在 React 中,render 方法会输出组件的 HTML。顶部应用栏组件将渲染 <header /> 标记,并由 2 个主要部分组成:
- 导航图标和标题部分
- “操作图标”部分
如果您对构成顶部应用栏的元素有疑问,请访问 GitHub 上的文档。
修改 TopAppBar.js 中的 render() 方法,使其如下所示:
render() {
const {
title,
navIcon,
} = this.props;
return (
<header
className={this.classes}
style={this.getMergedStyles()}
ref={this.topAppBarElement}
>
<div className='mdc-top-app-bar__row'>
<section className='mdc-top-app-bar__section mdc-top-app-bar__section--align-start'>
{navIcon ? navIcon : null}
<span className="mdc-top-app-bar__title">
{title}
</span>
</section>
{this.renderActionItems()}
</div>
</header>
);
}
此 HTML 中有两个 section 元素。第一个包含导航图标和标题。第二个包含操作图标。
接下来,添加 renderActionItems 方法:
renderActionItems() {
const {actionItems} = this.props;
if (!actionItems) {
return;
}
return (
<section className='mdc-top-app-bar__section mdc-top-app-bar__section--align-end' role='toolbar'>
{/* need to clone element to set key */}
{actionItems.map((item, key) => React.cloneElement(item, {key}))}
</section>
);
}
开发者会将 TopAppBar 导入到其 React 应用中,并将操作图标传递给 TopAppBar 元素。您可以在 App.js 中查看初始化 TopAppBar 的示例代码。
缺少 getMergedStyles 方法,该方法在 render 方法中使用。请将以下 JavaScript 方法添加到 TopAppBar 类中:
getMergedStyles = () => {
const {style} = this.props;
const {style: internalStyle} = this.state;
return Object.assign({}, internalStyle, style);
}
render 方法中也缺少 this.classes,但我们将在后面的部分中介绍。除了缺少 getter 方法 this.classes 之外,您还需要实现 TopAppBar 的其他部分,然后顶部应用栏才能正确呈现。
顶部应用栏中仍缺少以下 React 组件:
- 已初始化的基础
- 要传递到基础层的适配器方法
- JSX 标记
- 变体管理(固定、简短、始终处于收起状态、突出显示)
方法
- 实现 Adapter 方法。
- 在
componentDidMount中初始化 Foundation。 - 在
componentWillUnmount中调用 Foundation.destroy 方法。 - 通过组合适当的类名称的 getter 方法来建立变体管理。
4. 实现适配器方法
非框架 JS TopAppBar 组件实现了以下适配器方法(详细列表请点击此处):
hasClass()addClass()removeClass()registerNavigationIconInteractionHandler()deregisterNavigationIconInteractionHandler()notifyNavigationIconClicked()setStyle()getTopAppBarHeight()registerScrollHandler()deregisterScrollHandler()registerResizeHandler()deregisterResizeHandler()getViewportScrollY()getTotalActionItems()
由于 React 具有合成事件以及不同的最佳编码实践和模式,因此需要重新实现 Adapter 方法。
适配器 getter 方法
在 TopAppBar.js 文件中,将以下 JavaScript 方法添加到 TopAppBar:
get adapter() {
const {actionItems} = this.props;
return {
hasClass: (className) => this.classes.split(' ').includes(className),
addClass: (className) => this.setState({classList: this.state.classList.add(className)}),
removeClass: (className) => {
const {classList} = this.state;
classList.delete(className);
this.setState({classList});
},
setStyle: this.setStyle,
getTopAppBarHeight: () => this.topAppBarElement.current.clientHeight,
registerScrollHandler: (handler) => window.addEventListener('scroll', handler),
deregisterScrollHandler: (handler) => window.removeEventListener('scroll', handler),
registerResizeHandler: (handler) => window.addEventListener('resize', handler),
deregisterResizeHandler: (handler) => window.removeEventListener('resize', handler),
getViewportScrollY: () => window.pageYOffset,
getTotalActionItems: () => actionItems && actionItems.length,
};
}
用于注册滚动和调整大小事件的适配器 API 的实现方式与非框架 JS 版本相同,因为 React 没有用于滚动或调整大小的任何合成事件,而是会延迟到原生 DOM 事件系统。getViewPortScrollY 还需要延迟到原生 DOM,因为它是 window 对象上的一个函数,而 window 对象不在 React 的 API 中。每个框架的适配器实现方式都不同。
您可能会注意到,缺少由 get adapter 方法调用的 this.setStyle。在 TopAppBar.js 文件中,将缺少的 JavaScript 方法添加到 TopAppBar 类中:
setStyle = (varName, value) => {
const updatedStyle = Object.assign({}, this.state.style);
updatedStyle[varName] = value;
this.setState({style: updatedStyle});
}
您刚刚实现了 Adapter!请注意,您可能会在此阶段的控制台中看到错误,因为完整实现尚未完成。下一部分将引导您了解如何添加和移除 CSS 类。
5. 实现组件方法
管理变体和课程
React 没有用于管理类的 API。为了模仿原生 JavaScript 的添加/移除 CSS 类方法,请添加 classList 状态变量。TopAppBar 中有三段与 CSS 类互动的代码:
- 通过
className属性设置<TopAppBar />组件。 - 通过
addClass或removeClass实现的 Adapter 方法。 - 在
<TopAppBar />React 组件中硬编码。
首先,在 TopAppBar.js 顶部(现有 import 语句下方)添加以下 import 语句:
import classnames from 'classnames';
然后,在 TopAppBar 组件的类声明中添加以下代码:
export default class TopAppBar extends React.Component {
constructor(props) {
super(props);
this.topAppBarElement = React.createRef();
}
state = {
classList: new Set(),
style: {},
};
get classes() {
const {classList} = this.state;
const {
alwaysCollapsed,
className,
short,
fixed,
prominent,
} = this.props;
return classnames('mdc-top-app-bar', Array.from(classList), className, {
'mdc-top-app-bar--fixed': fixed,
'mdc-top-app-bar--short': short,
'mdc-top-app-bar--short-collapsed': alwaysCollapsed,
'mdc-top-app-bar--prominent': prominent,
});
}
...
}
如果您前往 http://localhost:8080,现在应该可以通过“控件”复选框在 DOM 中切换类名称的开启/关闭状态。
此代码可供许多开发者使用 TopAppBar。开发者可以与 TopAppBar API 进行交互,而无需担心 CSS 类的实现细节。
您现在已成功实现 Adapter。下一部分将引导您完成 Foundation 的实例化。
装载和卸载组件
基础实例化发生在 componentDidMount 方法中。
首先,通过在 TopAppBar.js 中添加以下 import(置于现有 import 之后)来导入 MDC 顶部应用栏基础:
import {MDCTopAppBarFoundation, MDCFixedTopAppBarFoundation, MDCShortTopAppBarFoundation} from '@material/top-app-bar';
接下来,将以下 JavaScript 代码添加到 TopAppBar 类中:
export default class TopAppBar extends React.Component {
...
foundation_ = null;
componentDidMount() {
this.initializeFoundation();
}
componentWillUnmount() {
this.foundation_.destroy();
}
initializeFoundation = () => {
if (this.props.short) {
this.foundation_ = new MDCShortTopAppBarFoundation(this.adapter);
} else if (this.props.fixed) {
this.foundation_ = new MDCFixedTopAppBarFoundation(this.adapter);
} else {
this.foundation_ = new MDCTopAppBarFoundation(this.adapter);
}
this.foundation_.init();
}
...
}
一种良好的 React 编码实践是定义 propTypes 和 defaultProps。在 TopAppBar.js 中,将以下 import 添加到现有 import 之后:
import PropTypes from 'prop-types';
然后,将以下代码添加到 TopAppBar.js 的底部(在 Component 类之后):
import PropTypes from 'prop-types';
TopAppBar.propTypes = {
alwaysCollapsed: PropTypes.bool,
short: PropTypes.bool,
fixed: PropTypes.bool,
prominent: PropTypes.bool,
title: PropTypes.string,
actionItems: PropTypes.arrayOf(PropTypes.element),
navIcon: PropTypes.element,
};
TopAppBar.defaultProps = {
alwaysCollapsed: false,
short: false,
fixed: false,
prominent: false,
title: '',
actionItems: null,
navIcon: null,
};
您现在已成功实现顶部应用栏 React 组件。如果您前往 http://localhost:8080,就可以试用演示页面。此演示网页的运作方式与 MDC Web 的演示网页相同。演示页面应如下所示:

6. 小结
在本教程中,我们介绍了如何封装 MDC Web 的 Foundation 以便在 React 应用中使用。GitHub 和 npm 上有一些库可以封装 MDC Web 组件,如集成到框架中中所述。我们建议您使用此处提供的列表。此列表还包括 React 以外的其他框架,例如 Angular 和 Vue。
本教程重点介绍了我们将 MDC Web 代码拆分为 3 个部分(即基础、适配器和组件)的决定。这种架构可让组件在与所有框架搭配使用时共享通用代码。感谢您试用 Material Components React,欢迎查看我们的新库 MDC React。希望您喜欢此 Codelab!