MDC-112 웹: MDC와 웹 프레임워크 통합

1. 소개

logo_components_color_2x_web_96dp.png

머티리얼 구성요소(MDC)를 통해 개발자는 머티리얼 디자인을 구현할 수 있습니다. Google의 엔지니어와 UX 디자이너로 구성된 팀에서 만든 MDC는 아름답고 기능적인 수십 가지의 UI 구성요소가 특징이며 Android, iOS, 웹, Flutter.material.io/develop에서 제공됩니다.

MDC Web은 Material Design 원칙을 준수하면서 모든 프런트엔드 프레임워크에 통합되도록 설계되었습니다. 다음 Codelab에서는 MDC Web을 기반으로 하는 React 구성요소를 빌드하는 방법을 안내합니다. 이 Codelab에서 배운 원칙은 모든 JavaScript 프레임워크에 적용할 수 있습니다.

MDC Web 빌드 방법

MDC Web의 JavaScript 레이어는 구성요소당 세 가지 클래스(Component, Foundation, Adapter)로 구성됩니다. 이 패턴을 사용하면 MDC Web을 프런트엔드 프레임워크와 유연하게 통합할 수 있습니다.

Foundation에는 Material Design을 구현하는 비즈니스 로직이 포함되어 있습니다. 재단은 HTML 요소를 참조하지 않습니다. 이를 통해 HTML 상호작용 로직을 어댑터로 추상화할 수 있습니다. Foundation에는 어댑터가 있습니다.

어댑터는 인터페이스입니다. 어댑터 인터페이스는 Foundation에서 Material Design 비즈니스 로직을 구현하는 데 참조됩니다. Angular 또는 React와 같은 다양한 프레임워크에서 어댑터를 구현할 수 있습니다. 어댑터 구현은 DOM 구조와 상호작용합니다.

구성요소에는 기반이 있으며 역할은

  1. 프레임워크가 아닌 JavaScript를 사용하여 어댑터를 구현합니다.
  2. Foundation의 메서드로 프록시하는 공개 메서드를 제공합니다.

MDC Web에서 제공하는 기능

MDC Web의 모든 패키지에는 구성요소, 기반, 어댑터가 포함됩니다. Component를 인스턴스화하려면 루트 element를 Component의 생성자 메서드에 전달해야 합니다. 구성요소는 DOM 및 HTML 요소와 상호작용하는 어댑터를 구현합니다. 그러면 구성요소어댑터 메서드를 호출하는 기반을 인스턴스화합니다.

MDC Web을 프레임워크에 통합하려면 해당 프레임워크의 언어/구문으로 자체 구성요소를 만들어야 합니다. 프레임워크 구성요소는 MDC Web의 어댑터를 구현하고 MDC Web의 기반을 사용합니다.

빌드할 항목

이 Codelab에서는 Foundation 로직을 사용하여 Material Design React 구성요소를 구현하는 맞춤 어댑터를 빌드하는 방법을 보여줍니다. 프레임워크에 통합에 있는 고급 주제를 다룹니다. 이 Codelab에서는 React를 예시 프레임워크로 사용하지만 이 접근 방식은 다른 프레임워크에도 적용할 수 있습니다.

이 Codelab에서는 상단 앱 바를 빌드하고 상단 앱 바 데모 페이지를 다시 만듭니다. 데모 페이지 레이아웃이 이미 설정되어 있으므로 상단 앱 바 작업을 시작할 수 있습니다. 상단 앱 바에는 다음이 포함됩니다.

  • 탐색 아이콘
  • 작업 항목
  • 짧은, 항상 접힘, 고정, 돌출 변형 등 4가지 변형이 있습니다.

필요한 항목:

  • 최신 버전의 Node.js (JavaScript 패키지 관리자인 npm과 함께 제공됨)
  • 샘플 코드 (다음 단계에서 다운로드)
  • HTML, CSS, JavaScript, React에 대한 기본 지식

웹 개발 경험 수준을 평가해 주세요.

초급 중급 고급

2. 개발 환경 설정

시작 Codelab 앱 다운로드

시작 앱은 material-components-web-codelabs-master/mdc-112/starter 디렉터리에 있습니다.

...또는 GitHub에서 클론

이 Codelab을 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 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 /> 태그를 렌더링하며 다음 두 가지 주요 섹션으로 구성됩니다.

  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에는 두 개의 섹션 요소가 있습니다. 첫 번째에는 탐색 아이콘과 제목이 포함됩니다. 두 번째에는 작업 아이콘이 포함됩니다.

다음으로 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>
  );
}

개발자가 React 애플리케이션으로 TopAppBar를 가져와 작업 아이콘을 TopAppBar 요소에 전달합니다. App.js에서 TopAppBar를 초기화하는 예시 코드를 확인할 수 있습니다.

render 메서드에서 사용되는 getMergedStyles 메서드가 누락되었습니다. TopAppBar 클래스에 다음 JavaScript 메서드를 추가하세요.

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

this.classesrender 메서드에서 누락되었지만 나중에 다루겠습니다. 누락된 getter 메서드 this.classes 외에도 상단 앱 바가 올바르게 렌더링되려면 아직 구현해야 하는 TopAppBar 부분이 있습니다.

상단 앱 바에서 아직 누락된 React 구성요소는 다음과 같습니다.

  • 초기화된 파운데이션
  • 파운데이션에 전달할 어댑터 메서드
  • JSX 마크업
  • 변형 관리 (고정, 짧음, 항상 접힘, 눈에 띔)

접근 방식

  1. 어댑터 메서드를 구현합니다.
  2. componentDidMount에서 Foundation을 초기화합니다.
  3. componentWillUnmount에서 Foundation.destroy 메서드를 호출합니다.
  4. 적절한 클래스 이름을 결합하는 getter 메서드를 통해 변형 관리를 설정합니다.

4. 어댑터 메서드 구현

프레임워크가 아닌 JS TopAppBar 구성요소는 다음 어댑터 메서드를 구현합니다 (자세한 내용은 여기 참고).

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

React에는 합성 이벤트와 다양한 코딩 권장사항 및 패턴이 있으므로 어댑터 메서드를 다시 구현해야 합니다.

어댑터 Getter 메서드

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 이벤트 시스템으로 지연되기 때문입니다. getViewPortScrollY는 React API에 없는 window 객체의 함수이므로 네이티브 DOM으로도 지연되어야 합니다. 어댑터 구현은 프레임워크마다 다릅니다.

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 클래스와 상호작용하는 세 가지 코드가 있습니다.

  1. className 속성을 통해 <TopAppBar /> 구성요소
  2. addClass 또는 removeClass을 통한 어댑터 메서드
  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';

다음으로 TopAppBar 클래스에 다음 JavaScript 코드를 추가합니다.

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

이제 상단 앱 바 React 구성요소를 구현했습니다. http://localhost:8080으로 이동하면 데모 페이지를 사용할 수 있습니다. 데모 페이지는 MDC Web 데모 페이지와 동일하게 작동합니다. 데모 페이지는 다음과 같이 표시됩니다.

3d983b98c2092e7a.png

6. 마무리

이 튜토리얼에서는 React 애플리케이션에서 사용할 MDC Web의 Foundation을 래핑하는 방법을 다루었습니다. 프레임워크에 통합에 설명된 대로 MDC 웹 구성요소를 래핑하는 라이브러리가 GitHub 및 npm에 몇 개 있습니다. 여기에서 목록을 확인하세요. 이 목록에는 React 외에도 Angular, Vue와 같은 다른 프레임워크도 포함됩니다.

이 튜토리얼에서는 MDC Web 코드를 Foundation, Adapter, Component의 세 부분으로 분할하기로 한 결정을 중점적으로 설명합니다. 이 아키텍처를 사용하면 구성요소가 모든 프레임워크와 함께 작동하면서 공통 코드를 공유할 수 있습니다. Material Components React를 사용해 주셔서 감사합니다. 새로운 라이브러리인 MDC React를 확인해 보세요. 이 Codelab에 만족하셨길 바랍니다.

적절한 시간과 노력을 들여 이 Codelab을 완료할 수 있었습니다.

매우 동의함 동의함 보통 동의하지 않음 전혀 동의하지 않음

앞으로 머티리얼 구성요소를 계속 사용하고 싶습니다.

매우 동의함 동의함 보통 동의하지 않음 전혀 동의하지 않음