MDC-112 Web: MDC とウェブ フレームワークの統合

1. はじめに

logo_components_color_2x_web_96dp.png

マテリアル コンポーネント(MDC)は、デベロッパーがマテリアル デザインを実装する際に役立ちます。Google のエンジニアと UX デザイナーのチームが作成した MDC には、美しく機能的な UI コンポーネントが多数含まれており、Android、iOS、ウェブ、Flutter.material.io/develop に利用可能です。

MDC Web は、マテリアル デザインの原則を維持しながら、任意のフロントエンド フレームワークに統合できるように設計されています。次の Codelab では、MDC Web を基盤として使用する React コンポーネントの作成手順について説明します。この Codelab で学んだ原則は、あらゆる JavaScript フレームワークに適用できます。

MDC ウェブの構築

MDC ウェブの JavaScript レイヤは、コンポーネントごとに ComponentFoundationAdapter の 3 つのクラスで構成されます。このパターンにより、MDC Web をフロントエンド フレームワークと柔軟に統合できます。

基盤には、マテリアル デザインを実装するビジネス ロジックが含まれています。基盤は HTML 要素を参照しません。これにより、HTML インタラクション ロジックをアダプタに抽象化できます。Foundation にはアダプタがあります。

Adapter はインターフェースです。アダプタ インターフェースは、マテリアル デザインのビジネス ロジックを実装するために Foundation によって参照されます。アダプターは、Angular や React などのさまざまなフレームワークで実装できます。アダプタの実装は DOM 構造を操作します。

コンポーネントには基盤があり、その役割は

  1. フレームワーク以外の JavaScript を使用して Adapter を実装します。
  2. Foundation のメソッドにプロキシする公開メソッドを指定します。

MDC Web が提供する機能

MDC Web のすべてのパッケージには、コンポーネント基盤アダプターが付属しています。コンポーネントをインスタンス化するには、ルート 要素をコンポーネントのコンストラクタ メソッドに渡す必要があります。コンポーネントは、DOM 要素と HTML 要素を操作するアダプターを実装します。次に、ComponentFoundation をインスタンス化し、Adapter メソッドを呼び出します。

MDC Web をフレームワークに統合するには、そのフレームワークの言語/構文で独自のコンポーネントを作成する必要があります。フレームワークのコンポーネントは MDC Web のアダプターを実装し、MDC Web の基盤を使用します。

作成するアプリの概要

この Codelab では、カスタム アダプターを構築して Foundation ロジックを使用してマテリアル デザインの React コンポーネントを実現する方法について説明します。ここでは、フレームワークへの統合で説明されている上級者向けトピックについて説明します。この Codelab ではフレームワークの例として React を使用していますが、このアプローチは他のフレームワークにも適用できます。

この Codelab では、上部アプリバーを作成して、上部アプリバーのデモページを再作成します。デモページのレイアウトはすでにセットアップされているため、トップアプリバーの作業を開始できます。トップ アプリバーには、次のものが含まれます。

  • ナビゲーション アイコン
  • アクション アイテム
  • バリエーションには、短い常に閉じられた固定目立つの 4 種類があります。

必要なもの:

  • 最新バージョンの Node.js(JavaScript パッケージ マネージャーである npm がバンドルされています)
  • サンプルコード(次の手順でダウンロード)
  • HTML、CSS、JavaScript、React に関する基本的な知識

ウェブ開発の経験についてお答えください。

初心者 中級者 上級者

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

多くのアクティビティが表示され、最後にターミナルにインストールが正常に完了したことが示されます。

22a33efc2a687408.png

スターター アプリを実行する

同じディレクトリで、次のコマンドを実行します。

npm start

webpack-dev-server が開始されます。ブラウザで http://localhost:8080/ にアクセスしてページを表示します。

b55c66dd400cf34f.png

完了しました。ブラウザで、Top App Bar React Demo ページのスターター コードが実行されているはずです。lorem ipsum のテキストの壁、[コントロール] ボックス(右下)、未完成のトップアプリバーが表示されます。

4ca3cf6d216f9290.png

コードとプロジェクトを確認します。

コードエディタを開くと、プロジェクト ディレクトリは次のようになります。

e9a3270d6a67c589.png

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 のエントリ ポイントです。

render メソッドを含む単純な React Component クラスである TopAppBar.js ファイルを開きます。

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.jsrender() メソッドを次のように変更します。

  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 には 2 つの section 要素があります。1 つ目はナビゲーション アイコンとタイトルを含みます。2 つ目にはアクション アイコンが含まれています。

次に、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 要素に渡します。TopAppBar を初期化するサンプルコードは App.js にあります。

render メソッドで使用される getMergedStyles メソッドがありません。次の JavaScript メソッドを TopAppBar クラスに追加してください。

getMergedStyles = () => {
  const {style} = this.props;
  const {style: internalStyle} = this.state;
  return Object.assign({}, internalStyle, style);
}

this.classesrender メソッドにも含まれていませんが、後のセクションで説明します。不足しているゲッター メソッド this.classes のほかに、トップ アプリバーを正しくレンダリングするために実装する必要がある TopAppBar の部分がまだあります。

トップ アプリバーにまだ含まれていない React コンポーネントの部分は次のとおりです。

  • 初期化された基盤
  • 基盤に渡すアダプタ メソッド
  • JSX マークアップ
  • パターン管理(固定、短い、常に閉じている、目立つ)

アプローチ

  1. Adapter メソッドを実装します。
  2. componentDidMountFoundation を初期化します。
  3. componentWillUnmountFoundation.destroy メソッドを呼び出します。
  4. 適切なクラス名を組み合わせるゲッター メソッドを介して、バリアント管理を確立します。

4. アダプタ メソッドを実装する

フレームワーク以外の JS TopAppBar コンポーネントは、次のアダプタメソッドを実装します(詳細はこちらをご覧ください)。

  • hasClass()
  • addClass()
  • removeClass()
  • registerNavigationIconInteractionHandler()
  • deregisterNavigationIconInteractionHandler()
  • notifyNavigationIconClicked()
  • setStyle()
  • getTopAppBarHeight()
  • registerScrollHandler()
  • deregisterScrollHandler()
  • registerResizeHandler()
  • deregisterResizeHandler()
  • getViewportScrollY()
  • getTotalActionItems()

React には合成イベントと異なるコーディングのベスト プラクティスとパターンがあるため、アダプター メソッドを再実装する必要があります。

アダプタのゲッター メソッド

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 イベント システムに委任するためです。また、getViewPortScrollYwindow オブジェクトの関数ですが、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 クラスを操作するコードが 3 つあります。

  1. <TopAppBar /> コンポーネントに className プロパティを渡します。
  2. addClass または removeClass を介した Adapter メソッド。
  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 にアクセスすると、[コントロール] チェックボックスで DOM のクラス名のオン/オフが切り替わるはずです。

このコードにより、多くのデベロッパーが TopAppBar を使用できるようになります。デベロッパーは、CSS クラスの実装の詳細を気にすることなく、TopAppBar API を操作できます。

これで、アダプタが正常に実装されました。次のセクションでは、Foundation のインスタンス化について説明します。

コンポーネントのマウントとマウントの解除

Foundation のインスタンス化は componentDidMount メソッドで行われます。

まず、TopAppBar.js の既存のインポートの後に次のインポートを追加して、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 のコーディングのベスト プラクティスの 1 つは、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 ウェブのデモページと同じように機能します。デモページは次のようになります。

3d983b98c2092e7a.png

6. まとめ

このチュートリアルでは、MDC Web の Foundation をラップして React アプリケーションで使用できるようにする方法について説明しました。フレームワークへの統合で説明されているように、GitHub と npm には MDC Web Components をラップするライブラリがいくつかあります。こちらのリストを使用することをおすすめします。このリストには、React 以外のフレームワーク(Angular、Vue など)も含まれています。

このチュートリアルでは、MDC ウェブコードを 3 つの部分(基盤アダプターコンポーネント)に分割するという Google の決定について説明します。このアーキテクチャにより、コンポーネントはすべてのフレームワークで動作しながら共通のコードを共有できます。Material Components React をお試しいただきありがとうございます。新しいライブラリ MDC React もぜひご確認ください。この Codelab がお役に立ちましたら幸いです。

この Codelab を完了するためにそれなりの時間と労力を必要とした

非常にそう思う そう思う どちらとも言えない そう思わない まったくそう思わない

今後もマテリアル コンポーネントを使用したい

非常にそう思う そう思う どちらとも言えない そう思わない まったくそう思わない