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 层由每个组件对应的三个类组成:组件、基础和适配器。此模式使 MDC Web 能够灵活地与前端框架集成。
基础包含实现 Material Design 的业务逻辑。该基础架构不会引用任何 HTML 元素。这样,我们就可以将 HTML 交互逻辑提取到适配器中。基础知识包含适配器。
适配器是一个接口。Adapter 接口由 Foundation 引用,以实现 Material Design 业务逻辑。您可以在不同的框架(例如 Angular 或 React)中实现适配器。适配器的实现会与 DOM 结构进行交互。
组件具有基础,其作用是
- 使用非框架 JavaScript 实现适配器,并
- 提供代理到 Foundation 中方法的公共方法。
MDC Web 提供的内容
MDC Web 中的每个软件包都包含一个组件、基础架构和适配器。如需实例化组件,您必须将根元素传递给组件的构造函数方法。Component 会实现 Adapter,后者能与 DOM 和 HTML 元素互动。然后,组件会实例化 Foundation,后者会调用 Adapter 方法。
如需将 MDC Web 集成到框架中,您需要使用该框架的语言/语法创建自己的组件。框架组件会实现 MDC Web 的适配器,并使用 MDC Web 的基础架构。
构建内容
此 Codelab 演示了如何构建自定义适配器,以使用 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 文本组成的墙、一个 Controls 框(位于右下角)和一个未完成的顶部应用栏:
查看代码和项目
如果您打开代码编辑器,项目目录应如下所示:
打开 App.js
文件,然后查看 render
方法(其中包含 <TopAppBar>
组件):
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
中查看用于在 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. 实现 Adapter 方法
非框架 JS TopAppBar
组件会实现以下 Adapter 方法(详见此处):
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
对象上的函数,而该对象不在 React 的 API 中。每个框架的适配器实现各不相同。
您可能会注意到缺少 this.setStyle
,该方法由 get adapter
方法调用。在 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 类交互:
<TopAppBar />
组件(通过className
属性)- 通过
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。
装载和卸载组件
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 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!