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 se integrar a qualquer framework de front-end, mantendo os princípios do Material Design. O codelab a seguir mostra como criar um componente React, que usa o MDC Web como base. Os princípios aprendidos neste codelab podem ser aplicados a qualquer framework de 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 oferece 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. O Foundation não faz referência a nenhum elemento HTML. Isso permite abstrair a lógica de interação HTML no Adapter. O Foundation tem um Adapter.

O Adapter é uma interface. A interface Adapter é 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 Component tem uma Foundation, e a função dele é

  1. Implemente o Adapter usando JavaScript sem framework e
  2. Forneça métodos públicos que façam proxy para métodos na Foundation.

O que o MDC Web oferece

Cada pacote no MDC Web vem com um Component, uma Foundation e um Adapter. Para instanciar um Component, transmita o elemento raiz ao método construtor do componente. O Component implementa um Adapter, que interage com o DOM e elementos HTML. O Component instancia o Foundation, que chama os métodos Adapter.

Para integrar o MDC Web a um framework, crie seu próprio Componente na linguagem/sintaxe desse framework. O framework Component implementa o Adapter do MDC Web e usa a Foundation do MDC Web.

O que você vai criar

Este codelab mostra como criar um Adapter personalizado para usar a lógica Foundation e criar um componente do Material Design React. Ele aborda os tópicos avançados encontrados em Integração 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 na parte de cima inclui:

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

O que é necessário:

  • Uma versão recente do Node.js (que vem com o npm, um gerenciador de pacotes JavaScript)
  • O exemplo de código (para ser baixado 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

O webpack-dev-server vai começar. Acesse http://localhost:8080/ no navegador para ver a página.

b55c66dd400cf34f.png

Pronto. O código inicial da página de demonstração do React da barra de apps principal será executado no navegador. Você vai ver uma parede de texto lorem ipsum, uma caixa Controles (parte de baixo à direita) e uma barra de apps na parte de cima 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 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 da barra de apps superior vai renderizar uma tag <header /> e será 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 principal, consulte 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. O primeiro contém um ícone e um título de navegação. 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 importa TopAppBar para o aplicativo React e transmite í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, que é 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);
}

this.classes também está ausente do 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 na parte de cima seja renderizada corretamente.

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

  • Uma fundação inicializada
  • Métodos de adaptador para transmitir à fundação
  • Marcação JSX
  • Gerenciamento de variantes (fixa, curta, sempre recolhida, em destaque)

A abordagem

  1. Implemente os métodos Adapter.
  2. Inicialize o 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 combina nomes de classes adequados.

4. Implementar métodos do adaptador

O Component TopAppBar JS sem framework implementa os seguintes métodos Adapter (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 adaptadoras para registro de eventos de rolagem e redimensionamento são implementadas de maneira idêntica à versão JS sem framework, porque o React não tem nenhum evento sintético para rolagem ou redimensionamento e usa o sistema de eventos DOM nativo. 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 do adaptador serão diferentes para cada framework.

Você vai notar que this.setStyle está faltando, 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. Neste momento, talvez você veja erros no console porque a implementação completa ainda não foi concluída. Na próxima seção, você vai aprender a 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 classe CSS de adição/remoção do JavaScript nativo, adicione a variável de estado classList. Há três partes do 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 seguinte importação na parte de cima de TopAppBar.js, abaixo das importações atuais:

import classnames from 'classnames';

Em seguida, adicione o seguinte código 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 "Controles" vão ativar/desativar os nomes de classe 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 adaptador. A próxima seção vai orientar você na instanciação de uma Foundation.

Montagem e desmontagem do componente

A instanciação do Foundation acontece no método componentDidMount.

Primeiro, importe os fundamentos da barra de apps principal 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 em 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 código abaixo à 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 React da barra de apps na parte superior. Se você acessar http://localhost:8080, poderá testar a página de demonstração. A página de demonstração vai funcionar da mesma forma que a página de demonstração do MDC Web. A página de demonstração vai ter esta aparência:

3d983b98c2092e7a.png

6. Conclusão

Neste tutorial, mostramos como encapsular a Fundação do MDC Web para uso em um aplicativo React. Há algumas bibliotecas no GitHub e no npm que encapsulam componentes da Web do MDC, conforme descrito em Integração com 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 do MDC Web em três partes: Foundation, Adapter e Component. Essa arquitetura permite que os componentes compartilhem um código comum enquanto trabalham com todos os frameworks. Agradecemos por testar os componentes do Material Design para 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