MDC-112 Web: Tích hợp MDC với Khung web

1. Giới thiệu

logo_components_color_2x_web_96dp.png

Thành phần Material (MDC) giúp nhà phát triển triển khai Material Design. Được tạo bởi một nhóm kỹ sư và nhà thiết kế trải nghiệm người dùng tại Google, MDC có hàng chục thành phần giao diện người dùng đẹp mắt và có chức năng, đồng thời có sẵn cho Android, iOS, web và Flutter.material.io/develop

MDC Web được thiết kế để tích hợp vào bất kỳ khung giao diện người dùng nào, đồng thời duy trì các nguyên tắc của Material Design. Lớp học lập trình sau đây sẽ hướng dẫn bạn cách tạo một Thành phần React sử dụng MDC Web làm nền tảng. Bạn có thể áp dụng các nguyên tắc đã học trong lớp học lập trình này cho bất kỳ khung JavaScript nào.

Cách xây dựng MDC Web

Lớp JavaScript của MDC Web bao gồm 3 lớp cho mỗi thành phần: Thành phần, Nền tảngBộ chuyển đổi. Mẫu này giúp MDC Web linh hoạt tích hợp với các khung giao diện người dùng.

Foundation chứa logic nghiệp vụ triển khai Material Design. Foundation không tham chiếu đến bất kỳ phần tử HTML nào. Điều này cho phép chúng ta trừu tượng hoá logic tương tác HTML vào Bộ chuyển đổi. Foundation có một Adapter (Bộ chuyển đổi).

Bộ chuyển đổi là một giao diện. Giao diện Trình chuyển đổi được Foundation tham chiếu để triển khai logic nghiệp vụ của Material Design. Bạn có thể triển khai Trình chuyển đổi trong nhiều khung như Angular hoặc React. Việc triển khai Bộ chuyển đổi tương tác với cấu trúc DOM.

Thành phần có một Foundation (Nền tảng) và vai trò của thành phần này là

  1. Triển khai Trình chuyển đổi, sử dụng JavaScript không phải khung và
  2. Cung cấp các phương thức công khai làm proxy cho các phương thức trong Foundation.

Những gì MDC Web cung cấp

Mỗi gói trong MDC Web đều có một Thành phần, Nền tảngBộ chuyển đổi. Để tạo thực thể cho một Thành phần, bạn phải truyền phần tử gốc đến phương thức hàm khởi tạo của Thành phần đó. Thành phần triển khai một Trình chuyển đổi, tương tác với các phần tử DOM và HTML. Sau đó, Component (Thành phần) sẽ tạo thực thể cho Foundation. Nền tảng này sẽ gọi phương thức Adapter.

Để tích hợp MDC Web vào một khung, bạn cần tạo Thành phần của riêng mình bằng ngôn ngữ/ngữ pháp của khung đó. Thành phần của khung này triển khai Trình chuyển đổi của MDC Web và sử dụng Nền tảng của MDC Web.

Sản phẩm bạn sẽ tạo ra

Lớp học lập trình này minh hoạ cách tạo một Trình chuyển đổi tuỳ chỉnh để sử dụng logic Foundation nhằm đạt được Thành phần React Material Design. Bài viết này đề cập đến các chủ đề nâng cao trong phần Tích hợp vào khung. React được dùng trong lớp học lập trình này làm khung mẫu, nhưng cũng có thể áp dụng cho bất kỳ khung nào khác.

Trong lớp học lập trình này, bạn sẽ tạo Thanh ứng dụng trên cùng và tạo lại trang minh hoạ thanh ứng dụng trên cùng. Bố cục trang minh hoạ đã được thiết lập để bạn có thể bắt đầu làm việc trên Thanh ứng dụng trên cùng. Thanh ứng dụng trên cùng sẽ bao gồm:

  • Biểu tượng đi theo chỉ dẫn
  • Mục hành động
  • Có 4 biến thể có sẵn: biến thể Ngắn, luôn thu gọn, cố địnhnổi bật

Những gì bạn cần:

  • Phiên bản gần đây của Node.js (đi kèm với npm, một trình quản lý gói JavaScript)
  • Mã mẫu (sẽ được tải xuống ở bước tiếp theo)
  • Kiến thức cơ bản về HTML, CSS, JavaScript và React

Bạn đánh giá thế nào về mức độ kinh nghiệm của mình trong việc phát triển web?

Tân binh Trung cấp Thành thạo

2. Thiết lập môi trường phát triển

Tải ứng dụng khởi đầu của lớp học lập trình

Ứng dụng khởi động nằm trong thư mục material-components-web-codelabs-master/mdc-112/starter.

...hoặc sao chép từ GitHub

Để sao chép lớp học lập trình này từ GitHub, hãy chạy các lệnh sau:

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

Cài đặt phần phụ thuộc dự án

Từ thư mục khởi động material-components-web-codelabs/mdc-112/starter, hãy chạy:

npm install

Bạn sẽ thấy nhiều hoạt động và cuối cùng, thiết bị đầu cuối sẽ hiển thị thông báo cài đặt thành công:

22a33efc2a687408.png

Chạy ứng dụng ban đầu

Trong cùng thư mục, hãy chạy:

npm start

webpack-dev-server sẽ bắt đầu. Trỏ trình duyệt của bạn đến http://localhost:8080/ để xem trang.

b55c66dd400cf34f.png

Thành công! Mã khởi đầu cho trang Bản minh hoạ phản ứng của thanh ứng dụng trên cùng phải đang chạy trong trình duyệt của bạn. Bạn sẽ thấy một bức tường văn bản lorem ipsum, một hộp Controls (Chế độ điều khiển) (dưới cùng bên phải) và một Thanh ứng dụng trên cùng chưa hoàn tất:

4ca3cf6d216f9290.png

Xem mã và dự án

Nếu bạn mở trình soạn thảo mã, thư mục dự án sẽ có dạng như sau:

e9a3270d6a67c589.png

Mở tệp App.js và xem phương thức render, trong đó có Thành phần <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>
    );
  }

Đây là điểm truy cập cho TopAppBar trong ứng dụng.

Mở tệp TopAppBar.js là một lớp React Component đơn thuần bằng phương thức render:

TopAppBar.js

import React from 'react';

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

3. Cấu trúc của thành phần

Trong React, phương thức render sẽ xuất ra HTML của Thành phần. Thành phần Thanh ứng dụng trên cùng sẽ hiển thị thẻ <header /> và bao gồm 2 phần chính:

  1. Phần tiêu đề và biểu tượng điều hướng
  2. Mục biểu tượng hành động

Nếu bạn có thắc mắc về các thành phần tạo nên Thanh ứng dụng trên cùng, hãy truy cập vào tài liệu trên GitHub.

Sửa đổi phương thức render() trong TopAppBar.js thành như sau:

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

Có hai phần tử section trong HTML này. Nội dung đầu tiên chứa biểu tượng điều hướng và tiêu đề. Thẻ thứ hai chứa các biểu tượng hành động.

Tiếp theo, hãy thêm phương thức 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>
  );
}

Nhà phát triển sẽ nhập TopAppBar vào ứng dụng React và truyền biểu tượng hành động đến phần tử TopAppBar. Bạn có thể xem mã ví dụ khởi chạy TopAppBar trong App.js.

Thiếu phương thức getMergedStyles được dùng trong phương thức render. Vui lòng thêm phương thức JavaScript sau đây vào lớp TopAppBar:

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

this.classes cũng bị thiếu trong phương thức render, nhưng sẽ được đề cập trong phần sau. Ngoài phương thức getter bị thiếu, this.classes, bạn vẫn cần triển khai một số phần của TopAppBar trước khi Thanh ứng dụng trên cùng có thể hiển thị chính xác.

Các phần của Thành phần React vẫn còn thiếu trong Thanh ứng dụng trên cùng là:

  • Một nền tảng đã khởi chạy
  • Các phương thức chuyển đổi để truyền vào nền tảng
  • Mã đánh dấu JSX
  • Quản lý biến thể (cố định, ngắn, luôn thu gọn, nổi bật)

Phương pháp tiếp cận

  1. Triển khai các phương thức Trình chuyển đổi.
  2. Khởi chạy Foundation trong componentDidMount.
  3. Gọi phương thức Foundation.destroy trong componentWillUnmount.
  4. Thiết lập cách quản lý biến thể thông qua một phương thức getter kết hợp các tên lớp phù hợp.

4. Triển khai các phương thức của Trình chuyển đổi

Thành phần TopAppBar JS không theo khung triển khai các phương thức Bộ chuyển đổi sau đây (được liệt kê chi tiết tại đây):

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

Vì React có các sự kiện tổng hợp và nhiều phương pháp và mẫu lập trình hay nhất, nên bạn cần triển khai lại các phương thức Trình chuyển đổi.

Phương thức getter của bộ chuyển đổi

Trong tệp TopAppBar.js, hãy thêm phương thức JavaScript sau đây vào 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,
  };
}

Các API bộ chuyển đổi để đăng ký sự kiện cuộn và đổi kích thước được triển khai giống hệt như phiên bản JS không có khung, vì React không có sự kiện tổng hợp nào để cuộn hoặc đổi kích thước và sẽ chuyển sang hệ thống sự kiện DOM gốc. getViewPortScrollY cũng cần phải trì hoãn DOM gốc vì đây là một hàm trên đối tượng window không có trong API của React. Cách triển khai bộ chuyển đổi sẽ khác nhau tuỳ theo từng khung.

Bạn có thể nhận thấy this.setStyle bị thiếu, phương thức này được gọi bởi phương thức get adapter. Trong tệp TopAppBar.js, hãy thêm phương thức JavaScript bị thiếu vào lớp TopAppBar:

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

Bạn vừa triển khai Bộ chuyển đổi! Xin lưu ý rằng tại thời điểm này, bạn có thể thấy lỗi trong bảng điều khiển vì quá trình triển khai đầy đủ chưa hoàn tất. Phần tiếp theo sẽ hướng dẫn bạn cách thêm và xoá các lớp CSS.

5. Triển khai các phương thức Thành phần

Quản lý biến thể và lớp

React không có API để quản lý lớp. Để mô phỏng các phương thức thêm/xoá lớp CSS của JavaScript gốc, hãy thêm biến trạng thái classList. Có ba đoạn mã trong TopAppBar tương tác với các lớp CSS:

  1. Thành phần <TopAppBar /> thông qua thuộc tính className.
  2. Phương thức Trình chuyển đổi thông qua addClass hoặc removeClass.
  3. Được mã hoá cứng trong Thành phần React <TopAppBar />.

Trước tiên, hãy thêm nội dung nhập sau vào đầu TopAppBar.js, bên dưới các nội dung nhập hiện có:

import classnames from 'classnames';

Sau đó, hãy thêm mã sau vào phần khai báo lớp của Thành phần 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,
    });
  }

  ... 
}

Nếu bạn chuyển đến http://localhost:8080, các hộp đánh dấu Controls (Chế độ điều khiển) hiện sẽ bật/tắt tên lớp từ DOM.

Mã này giúp nhiều nhà phát triển có thể sử dụng TopAppBar. Nhà phát triển có thể tương tác với API TopAppBar mà không phải lo lắng về các chi tiết triển khai của các lớp CSS.

Giờ thì bạn đã triển khai thành công Trình chuyển đổi. Phần tiếp theo sẽ hướng dẫn bạn tạo bản sao Foundation.

Gắn và tháo thành phần

Quá trình tạo thực thể nền diễn ra trong phương thức componentDidMount.

Trước tiên, hãy nhập các nền tảng Thanh ứng dụng trên cùng của MDC bằng cách thêm lệnh nhập sau đây sau các lệnh nhập hiện có trong TopAppBar.js:

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

Tiếp theo, hãy thêm mã JavaScript sau vào lớp 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();
  }
 
  ... 

}

Một phương pháp lập trình hay cho React là xác định propTypes và defaultProps. Thêm lệnh nhập sau đây sau các lệnh nhập hiện có trong TopAppBar.js:

import PropTypes from 'prop-types';

Sau đó, hãy thêm mã sau vào cuối TopAppBar.js (sau lớp 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,
};

Bạn đã triển khai thành công Thành phần React của Thanh ứng dụng trên cùng. Nếu chuyển đến http://localhost:8080, bạn có thể chơi với trang minh hoạ. Trang minh hoạ sẽ hoạt động giống như trang minh hoạ của MDC Web. Trang minh hoạ sẽ có dạng như sau:

3d983b98c2092e7a.pngS

6. Tóm tắt

Trong hướng dẫn này, chúng ta đã đề cập đến cách gói Foundation của MDC Web để sử dụng trong ứng dụng React. Có một số thư viện trên GitHub và npm bao bọc Thành phần web MDC như mô tả trong phần Tích hợp vào khung. Bạn nên sử dụng danh sách tại đây. Danh sách này cũng bao gồm các khung khác ngoài React, chẳng hạn như Angular và Vue.

Hướng dẫn này nêu bật quyết định của chúng tôi về việc chia mã Web MDC thành 3 phần, đó là Foundation (Nền tảng), Adapter (Bộ chuyển đổi) và Component (Thành phần). Cấu trúc này cho phép các thành phần chia sẻ mã chung trong khi làm việc với tất cả các khung. Cảm ơn bạn đã dùng thử Material Components React. Vui lòng tham khảo thư viện mới của chúng tôi MDC React. Chúng tôi hy vọng bạn đã thích lớp học lập trình này!

Tôi có thể hoàn thành lớp học lập trình này với thời gian và công sức hợp lý

Hoàn toàn đồng ý Đồng ý Trung lập Không đồng ý Hoàn toàn không đồng ý

Tôi muốn tiếp tục sử dụng Thành phần Material trong tương lai

Hoàn toàn đồng ý Đồng ý Trung lập Không đồng ý Hoàn toàn không đồng ý