MDC-112 Web:将 MDC 与 Web 框架集成

1. 简介

logo_components_color_2x_web_96dp.png

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 结构进行交互。

组件具有基础,其作用是

  1. 使用非框架 JavaScript 实现适配器,并
  2. 提供代理到 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,并且在最后,您的终端应该会显示已成功安装:

22a33efc2a687408

运行起始应用

在同一目录中,运行以下命令:

npm start

系统将启动 webpack-dev-server。将浏览器指向 http://localhost:8080/ 以查看该页面。

b55c66dd400cf34f.png

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

4ca3cf6d216f9290.png

查看代码和项目

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

e9a3270d6a67c589.png

打开 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 个主要部分组成:

  1. 导航图标和标题部分
  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 标记
  • 变体管理(固定、简短、始终收起、醒目)

采用的方法

  1. 实现 Adapter 方法。
  2. componentDidMount 中初始化 Foundation
  3. componentWillUnmount 中调用 Foundation.destroy 方法。
  4. 通过组合适当的类名称的 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 类交互:

  1. <TopAppBar /> 组件(通过 className 属性)
  2. 通过 addClassremoveClass 使用 Adapter 方法。
  3. <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 的演示页面相同。演示页面应如下所示:

3d983b98c2092e7a

6. 小结

在本教程中,我们介绍了如何封装 MDC Web 的 Foundation 以便在 React 应用中使用。GitHub 和 npm 上有一些库封装了 MDC Web 组件,如集成到框架中所述。我们建议您使用此处列出的列表。此列表还包含除 React 之外的其他框架,例如 Angular 和 Vue。

本教程重点介绍了我们决定将 MDC Web 代码拆分为 3 个部分:基础适配器组件。这种架构允许组件在与所有框架协同工作时共享通用代码。感谢您试用 Material Components React,请查看我们的新库 MDC React。希望您喜欢此 Codelab!

我能够用合理的时间和精力完成此 Codelab

非常赞同 赞同 中立 不赞同 非常不赞同

我希望日后继续使用 Material Components

非常同意 同意 中立 不同意 非常不同意