MDC-112 Web: интеграция MDC с веб-платформами

1. Введение

logo_components_color_2x_web_96dp.png

Компоненты Material (MDC) помогают разработчикам внедрять Material Design. Созданные командой инженеров и UX-дизайнеров Google, MDC включают в себя десятки красивых и функциональных компонентов пользовательского интерфейса и доступны для Android, iOS, веб-приложений и Flutter.material.io/develop

MDC Web разработан для интеграции с любым фронтенд-фреймворком, при этом сохраняя принципы Material Design. В этом практическом занятии вы научитесь создавать компонент React, используя MDC Web в качестве основы. Принципы, изученные в этом занятии, могут быть применены к любому JavaScript-фреймворку.

Как создается веб-интерфейс MDC

JavaScript-слой MDC Web состоит из трех классов для каждого компонента: Component , Foundation и Adapter . Такая структура обеспечивает MDC Web гибкость интеграции с фронтенд-фреймворками.

В Foundation содержится бизнес-логика, реализующая Material Design. Foundation не ссылается ни на какие HTML-элементы. Это позволяет нам абстрагировать логику взаимодействия с HTML в Adapter . Foundation имеет Adapter .

Адаптер — это интерфейс. Фонд использует интерфейс «Адаптер» для реализации бизнес-логики Material Design. Вы можете реализовать адаптер в различных фреймворках, таких как Angular или React. Реализация адаптера взаимодействует со структурой DOM.

Компонент имеет фундамент , и его роль заключается в том, чтобы

  1. Реализуйте адаптер , используя JavaScript без привязки к фреймворку, и
  2. Предоставьте общедоступные методы, которые будут выступать в качестве прокси для методов в Фонде .

Что предоставляет MDC Web

Каждый пакет в MDC Web включает в себя Component , Foundation и Adapter . Для создания экземпляра Component необходимо передать корневой элемент в метод конструктора Component. Component реализует интерфейс Adapter , который взаимодействует с элементами DOM и HTML. Затем Component создает экземпляр Foundation , который вызывает методы Adapter .

Для интеграции MDC Web в фреймворк необходимо создать собственный компонент на языке/синтаксисе этого фреймворка. Компонент фреймворка реализует интерфейс Adapter MDC Web и использует Foundation MDC Web.

Что вы построите

В этом практическом занятии показано, как создать собственный адаптер для использования логики Foundation с целью создания компонента React в стиле Material Design. Рассматриваются продвинутые темы, описанные в разделе «Интеграция во фреймворки» . В качестве примера в этом занятии используется React, но этот подход применим к любому другому фреймворку.

В этом практическом занятии вы создадите верхнюю панель приложений и воссоздадите демонстрационную страницу верхней панели приложений. Макет демонстрационной страницы уже настроен, поэтому вы можете начать работу над верхней панелью приложений. Верхняя панель приложений будет включать в себя:

  • значок навигации
  • Пункты плана действий
  • Доступны 4 варианта: короткий , всегда свернутый , фиксированный и выделенный .

Что вам понадобится:

  • Последняя версия Node.js (которая поставляется в комплекте с npm , менеджером пакетов JavaScript).
  • Пример кода (который будет загружен на следующем шаге)
  • Базовые знания HTML, CSS, JavaScript и React.

Как бы вы оценили свой уровень опыта в веб-разработке?

Новичок Средний Профессионал

2. Настройка среды разработки.

Скачайте стартовое приложение Codelab.

Стартовое приложение находится в каталоге 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

Успех! Стартовый код для демо-страницы Top App Bar 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 , который представляет собой простой класс Component React с методом render :

TopAppBar.js

import React from 'react';

export default class TopAppBar extends React.Component {
  render() {
    return (
      <header>
        TOP APP BAR
      </header>
    );
  }
}

3. Состав компонента

В React метод render выводит HTML-код компонента. Компонент Top App Bar отобразит тег ` <header /> и будет состоять из двух основных разделов:

  1. Значок навигации и раздел заголовка
  2. Раздел значков действий

Если у вас возникли вопросы об элементах, составляющих верхнюю панель приложений, посетите документацию на GitHub .

Измените метод render() в TopAppBar.js следующим образом:

  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 . Пример кода инициализации TopAppBar можно посмотреть в App.js

Отсутствует метод 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 , которые необходимо реализовать, прежде чем Top App Bar сможет корректно отображаться.

В компоненте React, которого до сих пор не хватает в Top App Bar, следующие элементы:

  • Первоначальный фундамент
  • Способы подключения адаптеров для прокладки кабелей в фундамент.
  • Разметка JSX
  • Управление вариантами (фиксированный, короткий, всегда свернутый, выделенный)

Подход

  1. Реализуйте методы адаптера .
  2. Инициализируйте Foundation в методе componentDidMount .
  3. Вызовите метод Foundation.destroy в компоненте componentWillUnmount .
  4. Организуйте управление вариантами с помощью метода-геттера, который объединяет соответствующие имена классов.

4. Реализуйте методы адаптера.

Компонент TopAppBar на JavaScript, не использующий фреймворк, реализует следующие методы адаптера (подробно описаны здесь ):

  • 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 адаптера для регистрации событий прокрутки и изменения размера реализованы идентично версии JavaScript без использования фреймворков, поскольку React не имеет синтетических событий для прокрутки или изменения размера и использует собственную систему событий DOM. getViewPortScrollY также должна использовать собственную систему DOM, поскольку она относится к объекту window , чего нет в API React. Реализации адаптеров будут различаться для каждого фреймворка.

Вы можете заметить, что отсутствует 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});
}

Вы только что реализовали адаптер ! Обратите внимание, что на этом этапе в консоли могут появляться ошибки, поскольку полная реализация еще не завершена. В следующем разделе мы расскажем, как добавлять и удалять CSS-классы.

5. Реализация методов компонента.

Управление вариантами и классами

В React нет API для управления классами. Чтобы имитировать методы добавления/удаления CSS-классов в нативном JavaScript, добавьте переменную состояния classList . В TopAppBar есть три фрагмента кода, которые взаимодействуют с CSS-классами:

  1. Компонент <TopAppBar /> через свойство className .
  2. Метод адаптера через addClass или removeClass .
  3. Это жестко закодировано внутри компонента React <TopAppBar /> .

Сначала добавьте следующий импорт в начало файла 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 доступным для многих разработчиков. Разработчики могут взаимодействовать с API TopAppBar , не беспокоясь о деталях реализации CSS-классов.

Вы успешно реализовали адаптер. В следующем разделе мы расскажем, как создать экземпляр Foundation .

Монтаж и демонтаж компонента

Создание экземпляра Foundation происходит в методе componentDidMount .

Сначала импортируйте базовые компоненты панели инструментов MDC Top App Bar, добавив следующий импорт после существующих импортов в TopAppBar.js :

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,
};

Вы успешно реализовали компонент Top App Bar React. Перейдя по адресу http://localhost:8080, вы можете протестировать демонстрационную страницу. Демонстрационная страница будет работать так же, как и демонстрационная страница MDC Web . Демонстрационная страница должна выглядеть следующим образом:

3d983b98c2092e7a.png

6. Подведение итогов

В этом руководстве мы рассмотрели, как обернуть Foundation от MDC Web для использования в приложении React. На Github и npm есть несколько библиотек, которые оборачивают компоненты MDC Web, как описано в разделе «Интеграция во фреймворки» . Мы рекомендуем использовать список, который можно найти здесь . Этот список также включает другие фреймворки, помимо React, такие как Angular и Vue.

В этом руководстве мы рассказываем о нашем решении разделить код MDC Web на 3 части: Foundation , Adapter и Component. Такая архитектура позволяет компонентам совместно использовать общий код и работать со всеми фреймворками. Спасибо, что попробовали Material Components React, и, пожалуйста, ознакомьтесь с нашей новой библиотекой MDC React . Надеемся, вам понравился этот мастер-класс!

Мне удалось выполнить это практическое задание за разумное время и с разумными затратами усилий.

Полностью согласен Соглашаться Нейтральный Не согласен Категорически не согласен

Я хотел бы и в будущем продолжать использовать компоненты Material.

Полностью согласен Соглашаться Нейтральный Не согласен Категорически не согласен