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

O MDC Web foi projetado para ser integrado a 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 o MDC Web como base. Os princípios aprendidos neste codelab podem ser aplicados a qualquer framework JavaScript.

Como o MDC Web é criado

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

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

O Adaptador é uma interface. A interface do adaptador é referenciada pela Foundation para implementar a lógica de negócios do Material Design. É possível implementar o Adaptador 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 adaptador usando JavaScript sem framework e
  2. Fornecer métodos públicos que representem métodos na Fundação.

O que os MDC Web oferece

Todos os pacotes do MDC Web vêm com um componente, uma estrutura 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. O Componente instancia a Foundation, que chama os métodos do Adaptador.

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 a Foundation do MDC Web.

O que você vai criar

Este codelab demonstra como criar um Adaptador personalizado para usar a lógica do Foundation e criar um componente do React do Material Design. Ele aborda os tópicos avançados em Como integrar a frameworks. O React é usado neste codelab como um exemplo de framework, 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 da barra de apps superior. O layout da página de demonstração já está configurado para que você possa começar a trabalhar na barra de apps na parte de cima. A barra de apps superior inclui:

  • Ícone de navegação
  • Ações necessárias
  • Há quatro variantes disponíveis: Curta, Sempre recolhida, Fixa e Proeminente.

O que você vai precisar:

  • Uma versão recente do Node.js (que vem junto com o npm, um gerenciador de pacotes JavaScript)
  • O exemplo de código (que será feito o download 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ê vai notar muita atividade e, no final, o terminal vai 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. Acesse http://localhost:8080/ no navegador para conferir a página.

b55c66dd400cf34f.png

Pronto. O código inicial da página de demonstração da barra de apps superior do React precisa estar em execução no seu 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

Confira o código e o projeto

Se você abrir o editor de código, o diretório do projeto vai ficar assim:

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 o TopAppBar no aplicativo.

Abra o arquivo TopAppBar.js, que é uma classe Component 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 da barra de apps superior renderiza uma tag <header /> e é composto por duas seções principais:

  1. Seção de ícone de navegação e 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 de navegação e um título. O segundo 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 no aplicativo React e transmitir ícones de ação para o elemento TopAppBar. Confira um exemplo de código inicializando um 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 não está no método render, mas 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 ausentes 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 Adapter.
  2. Inicialize a Foundation no componentDidMount.
  3. Chame o método Foundation.destroy no componentWillUnmount.
  4. Estabeleça o gerenciamento de variantes usando um método getter que combine os nomes de classe apropriados.

4. Implementar métodos do adaptador

O componente TopAppBar do JS sem 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, os métodos Adapter precisam ser implementados novamente.

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 do adaptador para registro de eventos de rolagem e redimensionamento são implementadas de forma idêntica à versão do JS sem framework, porque o React não tem nenhum evento sintético para rolagem ou redimensionamento e adia para o sistema de eventos DOM nativo. O getViewPortScrollY também precisa ser adiado para o DOM nativo, já que é uma função no objeto window, que não está na API do React. As implementações de adaptador serão diferentes para cada framework.

O this.setStyle está ausente, o 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 Adaptador. Talvez você encontre erros no console, porque a implementação completa ainda não foi concluída. A próxima seção vai mostrar como adicionar e remover classes CSS.

5. Implementar métodos de 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 de classes CSS do JavaScript nativo, adicione a variável de estado classList. Há três partes do código em TopAppBar que interagem com as classes CSS:

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

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

import classnames from 'classnames';

Em seguida, adicione o seguinte código à 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.

Agora você já implementou o adaptador. Na próxima seção, vamos mostrar como instanciar uma Foundation.

Como montar e desconectar o componente

A instanciação da Foundation 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 existentes 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 importação a seguir 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. Se você acessar http://localhost:8080, poderá brincar com a página de demonstração. A página de demonstração vai funcionar da mesma forma que a página de demonstração da MDC Web. A página de demonstração vai ficar assim:

3d983b98c2092e7a.png

6. Conclusão

Neste tutorial, vimos como unir o Foundation do MDC Web para uso em um aplicativo React. Há algumas bibliotecas no Github e no npm que envolvem os componentes da Web do MDC, conforme descrito em Integração em frameworks. Recomendamos que você use a lista disponível 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 o código comum ao trabalhar 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 Não concordo nem discordo Discordo Discordo totalmente