MDC-112 na Web: como integrar o MDC com frameworks da Web

1. Introdução

logo_components_color_2x_web_96dp.png

Os componentes do Material Design (MDC, na sigla em inglês) ajudam os desenvolvedores a implementar o Material Design. Criados por uma equipe de engenheiros e designers de UX do Google, os MDC apresentam dezenas de componentes de interface bonitos e funcionais e estão disponíveis para Android, iOS, Web e Flutter.material.io/develop

Os MDC Web foram projetados para integrar-se em qualquer framework de front-end, mantendo os princípios do Material Design. O codelab a seguir orienta você na criação de um componente React, que usa a Web MDC como base. Os princípios aprendidos neste codelab podem ser aplicados a qualquer framework JavaScript.

Como a Web dos MDC é criada

A camada JavaScript do MDC Web é composta por três classes por componente: Component, Foundation e Adapter. Esse padrão dá aos MDC Web a flexibilidade para se integrar com frameworks de front-end.

A Fundação contém a lógica de negócios que implementa o Material Design. A base não faz referência a nenhum elemento HTML. Isso nos permite abstrair a lógica de interação HTML no Adapter. O elemento Foundation tem um Adapter.

O Adapter é uma interface. A interface Adapter é referenciada pela Foundation (link em inglês) para implementar a lógica de negócios do Material Design. Você pode implementar o Adapter em diferentes frameworks, como Angular ou React. Uma implementação de um adaptador interage com a estrutura do DOM.

O Componente tem uma fundação e a função dele é

  1. Implemente o Adapter usando um JavaScript sem framework.
  2. Fornecer métodos públicos que representem métodos na Fundação.

O que os MDC Web oferece

Todos os pacotes no MDC Web vêm com um componente, uma fundação e um adaptador. Para instanciar um Component, transmita o element raiz ao método construtor do componente. O Component implementa um Adapter, que interage com os elementos DOM e HTML. Em seguida, o Component instancia o Foundation, que chama os métodos do Adapter.

Para integrar os MDC Web a um framework, você precisa criar seu próprio componente na linguagem/sintaxe desse framework. O Component do framework implementa o Adapter do MDC Web e usa o Foundation do MDC Web.

O que você vai criar

Este codelab demonstra como criar um adaptador personalizado para usar a lógica Foundation e criar um componente React do Material Design. Ele aborda os tópicos avançados em Como integrar a frameworks. Neste codelab, o React é usado como um framework de exemplo, mas essa abordagem pode ser aplicada a qualquer outro framework.

Neste codelab, você vai criar a barra de apps superior e recriar a página de demonstração dela. O layout da página de demonstração já está configurado, então você pode começar a trabalhar na barra de apps superior. A barra de apps superior inclui o seguinte:

  • Ícone de navegação
  • Ações necessárias
  • Há quatro variantes disponíveis: Short, sempre recolhida, fixa e em destaque

O que é necessário:

  • Uma versão recente do Node.js (que acompanha o NPM, um gerenciador de pacotes JavaScript)
  • O exemplo de código (que será feito na próxima etapa)
  • Conhecimento básico de HTML, CSS, JavaScript e React.

Como você classificaria seu nível de experiência com desenvolvimento da Web?

Iniciante Intermediário Proficiente

2. Configurar o ambiente de desenvolvimento

Faça o download do app inicial do codelab

O app inicial está localizado no diretório material-components-web-codelabs-master/mdc-112/starter.

... ou clone-o do GitHub

Para clonar este codelab do GitHub, execute estes comandos:

git clone https://github.com/material-components/material-components-web-codelabs
cd material-components-web-codelabs/mdc-112/starter

Instalar dependências do projeto

No diretório inicial material-components-web-codelabs/mdc-112/starter, execute:

npm install

Você verá muitas atividades e, no final, o terminal mostrará uma instalação bem-sucedida:

22a33efc2a687408.png

Executar o app inicial

No mesmo diretório, execute:

npm start

A webpack-dev-server será iniciada. No navegador, acesse http://localhost:8080/ para ver a página.

b55c66dd400cf34f.png

Pronto. O código inicial da página de demonstração do Top App Bar React será executado no navegador. Você verá uma parede com o texto lorem ipsum, uma caixa Controles (canto inferior direito) e uma barra de apps superior inacabada:

4ca3cf6d216f9290.png

Conferir o código e o projeto

Se você abrir o editor de código, o diretório do projeto terá esta aparência:

e9a3270d6a67c589.png

Abra o arquivo App.js e observe o método render, que inclui o componente <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>
    );
  }

Esse é o ponto de entrada para TopAppBar no aplicativo.

Abra o arquivo TopAppBar.js, que é uma classe Component do React simples com um método render:

TopAppBar.js

import React from 'react';

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

3. Composição do componente

No React, o método render gera o HTML do componente. O componente "Barra de apps superior" renderiza uma tag <header /> e é composto por duas seções principais:

  1. Ícone de navegação e seção de título
  2. Seção de ícones de ação

Se você tiver dúvidas sobre os elementos que compõem a barra de apps superior, acesse a documentação no GitHub.

Modifique o método render() em TopAppBar.js para que fique assim:

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

Há dois elementos de seção neste HTML. A primeira contém um ícone e um título de navegação. A segunda contém ícones de ação.

Em seguida, adicione o método 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>
  );
}

Um desenvolvedor vai importar TopAppBar para o aplicativo React e transmitir ícones de ação para o elemento TopAppBar. Confira um exemplo de código inicializando TopAppBar em App.js.

O método getMergedStyles está ausente e é usado no método render. Adicione o seguinte método JavaScript à classe TopAppBar:

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

O this.classes também está ausente no método render, mas isso será abordado em uma seção posterior. Além do método getter ausente, this.classes, ainda há partes do TopAppBar que você precisa implementar para que a barra de apps superior possa ser renderizada corretamente.

As partes do componente React que ainda estão faltando na barra de apps superior são:

  • Uma base inicializada
  • Métodos do adaptador a serem transmitidos para a base
  • Marcação JSX
  • Gerenciamento de variantes (fixo, curto, sempre recolhido, em destaque)

A abordagem

  1. Implemente os métodos do Adapter.
  2. Inicialize a Foundation no componentDidMount.
  3. Chame o método Foundation.destroy no componentWillUnmount.
  4. Estabeleça o gerenciamento de variantes com um método getter que combina os nomes de classe apropriados.

4. Implementar métodos do adaptador

O componente JS TopAppBar que não é framework implementa os seguintes métodos de adaptador (listados em detalhes aqui):

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

Como o React tem eventos sintéticos e diferentes práticas e padrões de programação recomendados, os métodos Adapter precisam ser reimplementados.

Método getter do adaptador

No arquivo TopAppBar.js, adicione o seguinte método JavaScript a 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,
  };
}

As APIs de adaptador para registro de eventos de rolagem e redimensionamento são implementadas de forma idêntica à versão JS sem framework, porque o React não tem nenhum evento sintético para rolagem ou redimensionamento e adere ao sistema de eventos DOM nativo. O getViewPortScrollY também precisa respeitar o DOM nativo, já que é uma função no objeto window, que não está na API React. As implementações de adaptadores serão diferentes para cada framework.

Você pode notar que o this.setStyle está ausente, que é chamado pelo método get adapter. No arquivo TopAppBar.js, adicione o método JavaScript ausente à classe TopAppBar:

setStyle = (varName, value) => {
  const updatedStyle = Object.assign({}, this.state.style);
  updatedStyle[varName] = value;
  this.setState({style: updatedStyle});
}

Você acabou de implementar o Adapter. Talvez você veja erros no seu console neste momento porque a implementação completa ainda não está concluída. Na próxima seção, vamos mostrar como adicionar e remover classes CSS.

5. Implementar métodos dos componentes

Como gerenciar variantes e classes

O React não tem uma API para gerenciar classes. Para imitar os métodos de adição/remoção da classe CSS do JavaScript nativo, adicione a variável de estado classList. Há três partes de código em TopAppBar que interagem com classes CSS:

  1. Componente <TopAppBar /> usando a propriedade className.
  2. O método Adapter via addClass ou removeClass.
  3. Codificado no componente <TopAppBar /> do React.

Primeiro, adicione a importação abaixo na parte de cima de TopAppBar.js, abaixo das importações atuais:

import classnames from 'classnames';

Em seguida, adicione o código abaixo na declaração de classe do componente 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,
    });
  }

  ... 
}

Se você acessar http://localhost:8080, as caixas de seleção dos controles devem ativar/desativar os nomes de classes do DOM.

Esse código torna o TopAppBar utilizável por muitos desenvolvedores. Os desenvolvedores podem interagir com a API TopAppBar sem se preocupar com os detalhes de implementação das classes CSS.

Você implementou o Adapter com sucesso. Na próxima seção, vamos mostrar como instanciar uma Foundation.

Como montar e desconectar o componente

A instanciação da base acontece no método componentDidMount.

Primeiro, importe as bases da barra de apps superior do MDC adicionando a seguinte importação após as importações atuais em TopAppBar.js:

import {MDCTopAppBarFoundation, MDCFixedTopAppBarFoundation, MDCShortTopAppBarFoundation} from '@material/top-app-bar';

Em seguida, adicione o seguinte código JavaScript à classe 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();
  }
 
  ... 

}

Uma boa prática de programação do React é definir propTypes e defaultProps. Adicione a seguinte importação após as importações atuais em TopAppBar.js:

import PropTypes from 'prop-types';

Em seguida, adicione o seguinte código à parte de baixo de TopAppBar.js, depois da classe 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,
};

Você implementou o componente de reação da barra de apps superior. Acesse http://localhost:8080 para acessar a página de demonstração. A página de demonstração funcionará da mesma forma que a página de demonstração da Web do MDC. A página de demonstração vai ficar assim:

3d983b98c2092e7a.png

6. Resumo

Neste tutorial, abordamos como unir o Foundation do MDC Web para uso em um aplicativo React. Existem algumas bibliotecas no GitHub e no npm que unem os MDC Web Components, conforme descrito em Como integrar a frameworks. Recomendamos que você use a lista aqui. Essa lista também inclui outros frameworks além do React, como Angular e Vue.

Este tutorial destaca nossa decisão de dividir o código da Web MDC em três partes: fundação, adaptador e componente. Essa arquitetura permite que os componentes compartilhem código comum enquanto trabalham com todos os frameworks. Agradecemos por testar o Material Components React. Confira nossa nova biblioteca, MDC React. Esperamos que tenha gostado deste codelab.

Este codelab exigiu esforço e tempo normais para ser concluído

Concordo totalmente Concordo Não concordo nem discordo Discordo Discordo totalmente

Quero continuar usando componentes do Material Design no futuro

Concordo totalmente Concordo Neutro Discordo Discordo totalmente