MDC-112 網路:整合 MDC 與 Web 架構

1. 簡介

logo_components_color_2x_web_96dp.png

Material 元件 (MDC) 可協助開發人員實作 Material Design。MDC 是由 Google 的工程師和使用者體驗設計師團隊所開發,提供數十種美觀實用的 UI 元件,適用於 Android、iOS、網頁和 Flutter。material.io/develop

MDC Web 的設計目的是整合至任何前端架構,同時維持 Material Design 原則。以下程式碼研究室會引導您建構 React 元件,該元件會使用 MDC Web 做為基礎。本程式碼研究室中學到的原則,可套用至任何 JavaScript 架構。

MDC Web 的建構方式

MDC Web 的 JavaScript 層的每個元件由三個類別組成:元件基礎轉接器。這個模式可讓 MDC Web 彈性地整合前端架構。

Foundation 包含實作 Material Design 的商業邏輯。基礎架構不會參照任何 HTML 元素。這可讓我們將 HTML 互動邏輯抽象化為AdapterFoundationAdapter

Adapter 是介面。Foundation 會參照 Adapter 介面,以便實作 Material Design 業務邏輯。您可以使用 Angular 或 React 等其他架構實作轉接程式。轉接程式的實作會與 DOM 結構互動。

元件具有基礎,其角色是

  1. 導入轉接程式 (使用非架構 JavaScript),並
  2. 提供公開方法,以 Proxy 為基礎中的方法。

MDC Web 提供的內容

MDC Web 中的每個套件都會隨附元件基礎轉接器。如要將 Component 例項化,您必須將根元素傳遞至 Component 的建構函式方法。元件會實作轉接程式,用於與 DOM 和 HTML 元素互動。接著,Component 會將 Foundation 例項化,並呼叫 Adapter 方法。

如要將 MDC Web 整合至架構,您必須使用該架構的語言/語法建立自己的元件。架構 Component 會實作 MDC Web 的 Adapter,並使用 MDC Web 的 Foundation

建構項目

本程式碼研究室將示範如何建構自訂的Adapter,以便使用Foundation 邏輯來實現 Material Design React 元件。這門課程涵蓋「整合至架構」中的進階主題。本程式碼研究室使用 React 做為範例架構,但這項做法可套用至任何其他架構。

在本程式碼研究室中,您將建構頂端應用程式列,並重新建立頂端應用程式列示範頁面。示範頁面版面配置已設定完成,因此您可以開始使用頂端應用程式列。頂端應用程式列包括:

  • 導覽圖示
  • 待辦事項
  • 目前提供 4 種變化版本:「短」、「一律收合」、「固定」和「顯眼」的變化版本

事前準備

  • 最新版 Node.js (隨附 npm,這是 JavaScript 套件管理工具)
  • 程式碼範例 (將於下一個步驟下載)
  • 具備 HTML、CSS、JavaScript 和 React 的基本知識

針對網站開發的經驗程度,你會給予什麼評價?

新手 中級 熟練

2. 設定開發環境

下載程式碼研究室入門應用程式

範例應用程式位於 material-components-web-codelabs-master/mdc-112/starter 目錄中。

...或從 GitHub 複製

如要從 GitHub 複製本程式碼研究室,請執行下列指令:

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

您會看到許多活動,最後終端機應會顯示安裝成功:

22a33efc2a687408.png

執行範例應用程式

在同一個目錄中執行:

npm start

webpack-dev-server 就會開始。將瀏覽器指向 http://localhost:8080/ 即可查看該頁面。

b55c66dd400cf34f.png

大功告成!頂端應用程式列 React 示範頁面的啟動程式碼應會在瀏覽器中執行。您應該會看到一整面 lorem ipsum 文字、一個 控制項方塊 (右下方),以及未完成的頂端應用程式列:

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 中查看初始化 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 Component 會實作下列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 中。每個架構的轉接程式實作方式都不同。

您可能會發現缺少由 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});
}

您剛剛已實作轉接程式!請注意,目前您的控制台可能會顯示錯誤,因為完整的實作尚未完成。下一節將說明如何新增及移除 CSS 類別。

5. 實作元件方法

管理變化版本和類別

React 沒有可管理類別的 API,如要模擬原生 JavaScript 的 CSS 類別新增/移除方法,請新增 classList 狀態變數。TopAppBar 中有三個與 CSS 類別互動的程式碼:

  1. <TopAppBar /> 元件,並透過 className 屬性。
  2. 透過 addClassremoveClassAdapter 方法。
  3. <TopAppBar /> React 元件內使用硬式編碼。

首先,在 TopAppBar.js 頂端的現有匯入項目下方新增下列匯入項目:

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,「Control」核取方塊現在應切換為開啟/關閉 DOM 的類別名稱。

這段程式碼可讓許多開發人員使用 TopAppBar。開發人員可以與 TopAppBar API 互動,不必擔心 CSS 類別的實作細節。

您已成功實作轉接程式。下一節將引導您建構 Foundation 的例項。

掛載及卸載元件

Foundation 會在 componentDidMount 方法中進行例項化。

首先,請在 TopAppBar.js 現有的匯入項目後方新增下列匯入項目,匯入 MDC Top App Bar 基礎:

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();
  }
 
  ... 

}

定義 propTypes 和 defaultProps 是良好的 React 程式設計做法。在 TopAppBar.js 中現有的匯入項目後方,新增下列匯入項目:

import PropTypes from 'prop-types';

接著,在 TopAppBar.js 底部 (元件類別之後) 新增下列程式碼:

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.png

6. 總結

在本教學課程中,我們將說明如何包裝 MDC Web 的 Foundation,以便在 React 應用程式中使用。GitHub 和 npm 中的幾個程式庫會包裝 MDC Web 元件,如整合至架構中所述。建議您使用這裡的清單。除了 React 之外,這份清單也包含其他架構,例如 Angular 和 Vue。

本教學課程重點說明我們決定將 MDC Web 程式碼分成 3 個部分,分別是「基礎」、「轉接器」和「元件」。此架構可讓元件在與所有架構搭配運作時,共用通用程式碼。感謝您試用 Material Components React,歡迎查看我們的新程式庫 MDC React。希望您喜歡這個程式碼研究室!

我能夠在合理的時間和精力下完成本程式碼研究室

非常同意 同意 普通 不同意 非常不同意

我想日後繼續使用 Material Design 元件

非常同意 同意 普通 不同意 非常不同意