MDC-112 เว็บ: การผสานรวม MDC กับเว็บเฟรมเวิร์ก

1. บทนำ

logo_components_color_2x_web_96dp.png

Material Components (MDC) ช่วยให้นักพัฒนานำดีไซน์ Material มาใช้ MDC สร้างโดยทีมวิศวกรและนักออกแบบ UX ที่ Google โดยมีคอมโพเนนต์ UI ที่สวยงามและใช้งานได้หลายสิบอย่างและพร้อมใช้งานสำหรับ Android, iOS, เว็บ และ Flutter.material.io/develop

MDC Web ได้รับการออกแบบมาเพื่อผสานรวมเข้ากับเฟรมเวิร์กฟรอนท์เอนด์พร้อมกับสนับสนุนหลักการของดีไซน์ Material Codelab ต่อไปนี้จะแนะนำวิธีสร้างคอมโพเนนต์ React ซึ่งใช้ MDC Web เป็นรากฐาน หลักการที่เรียนรู้ใน Codelab นี้สามารถนำไปใช้กับเฟรมเวิร์ก JavaScript ใดก็ได้

การสร้างเว็บ MDC

เลเยอร์ JavaScript ของเว็บ MDC ประกอบด้วย 3 คลาสต่อคอมโพเนนต์ ได้แก่ คอมโพเนนต์ รากฐาน และอะแดปเตอร์ รูปแบบนี้ช่วยให้เว็บ MDC มีความยืดหยุ่นในการผสานรวมกับเฟรมเวิร์กฟรอนท์เอนด์

พื้นฐานมีตรรกะทางธุรกิจที่นำดีไซน์ Material มาใช้ มูลนิธิไม่ได้อ้างอิงองค์ประกอบ HTML ใดๆ ซึ่งจะทำให้เราทำตรรกะการโต้ตอบ HTML ลงในอะแดปเตอร์ได้ Foundation มีอะแดปเตอร์

อะแดปเตอร์เป็นอินเทอร์เฟซ พื้นฐานอ้างอิงอินเทอร์เฟซอะแดปเตอร์เพื่อนำตรรกะทางธุรกิจของดีไซน์ Material มาใช้ คุณสามารถใช้อะแดปเตอร์ในเฟรมเวิร์กที่แตกต่างกัน เช่น Angular หรือ React การใช้งานอะแดปเตอร์โต้ตอบกับโครงสร้าง DOM

คอมโพเนนต์มีรากฐานซึ่งมีหน้าที่ในการ

  1. ติดตั้งอะแดปเตอร์โดยใช้ JavaScript แบบไม่ใช้เฟรมเวิร์ก และ
  2. ระบุเมธอดสาธารณะที่พร็อกซีกับเมธอดในพื้นฐาน

สิ่งที่ MDC Web มีให้

แพ็กเกจทั้งหมดใน MDC Web มาพร้อมกับคอมโพเนนต์ พื้นฐาน และอะแดปเตอร์ ในการสร้างอินสแตนซ์ของคอมโพเนนต์ คุณต้องส่งองค์ประกอบรากไปยังเมธอดตัวสร้างของคอมโพเนนต์ คอมโพเนนต์จะใช้อะแดปเตอร์ซึ่งโต้ตอบกับองค์ประกอบ DOM และ HTML จากนั้น Component จะสร้างอินสแตนซ์ Foundation ซึ่งเรียกเมธอด Adapter

หากต้องการผสานรวม MDC Web ไว้ในเฟรมเวิร์ก คุณต้องสร้างคอมโพเนนต์ของคุณเองในภาษา/ไวยากรณ์ของเฟรมเวิร์กนั้น Component ของเฟรมเวิร์กจะใช้อะแดปเตอร์ของ MDC Web และใช้พื้นฐานของ MDC Web

สิ่งที่คุณจะสร้าง

Codelab นี้สาธิตวิธีสร้างอะแดปเตอร์ที่กำหนดเองเพื่อใช้ตรรกะพื้นฐานในการสร้างคอมโพเนนต์ Material Design React ซึ่งครอบคลุมหัวข้อขั้นสูงในหัวข้อการผสานรวมกับเฟรมเวิร์ก มีการใช้ React ใน Codelab นี้เป็นเฟรมเวิร์กตัวอย่าง แต่คุณจะนำวิธีนี้ไปใช้กับเฟรมเวิร์กอื่นๆ ได้ด้วย

ใน Codelab นี้ คุณจะสร้างแถบแอปด้านบนและสร้างหน้าสาธิตแถบแอปด้านบนใหม่ เลย์เอาต์หน้าสาธิตได้รับการตั้งค่าไว้แล้วเพื่อให้คุณเริ่มทํางานบนแถบแอปด้านบนได้ แถบแอปด้านบนจะมีข้อมูลต่อไปนี้

  • ไอคอนการนำทาง
  • รายการการทำงาน
  • มีตัวแปร 4 แบบ ได้แก่ ตัวแปรสั้น ยุบเสมอ คงที่ และโดดเด่น

สิ่งที่คุณต้องมี

  • Node.js เวอร์ชันล่าสุด (ซึ่งมาพร้อมกับ npm ซึ่งเป็นเครื่องมือจัดการแพ็กเกจ JavaScript)
  • โค้ดตัวอย่าง (จะดาวน์โหลดในขั้นตอนถัดไป)
  • ความรู้พื้นฐานเกี่ยวกับ 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

ติดตั้งทรัพยากร Dependency ของโปรเจ็กต์

จากไดเรกทอรีเริ่มต้น 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 เดโมควรจะทำงานอยู่ในเบราว์เซอร์ของคุณ คุณควรเห็นแผงข้อความ lorem ipsum, ช่อง Controls (ด้านล่างขวา) และแถบแอปด้านบนที่ยังไม่เสร็จสมบูรณ์:

4ca3cf6d216f9290.png

ดูโค้ดและโปรเจ็กต์

หากคุณเปิดตัวแก้ไขโค้ด ไดเรกทอรีโปรเจ็กต์ควรมีลักษณะดังนี้

e9a3270d6a67c589.png

เปิดไฟล์ App.js และดูเมธอด render ซึ่งมีคอมโพเนนต์ <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>
    );
  }

นี่คือจุดแรกเข้าสำหรับ TopAppBar ในแอปพลิเคชัน

เปิดไฟล์ TopAppBar.js ซึ่งเป็นคลาส React Component เปล่าโดยใช้เมธอด render:

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 /> และประกอบด้วยส่วนหลัก 2 ส่วนดังนี้

  1. ส่วนไอคอนการนำทางและชื่อการนำทาง
  2. ส่วนไอคอนการทำงาน

หากมีข้อสงสัยเกี่ยวกับองค์ประกอบที่ประกอบเป็นแถบแอปด้านบน โปรดดูเอกสารประกอบใน GitHub

แก้ไขเมธอด render() ใน TopAppBar.js ให้มีลักษณะดังนี้

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

มีองค์ประกอบ 2 ส่วนใน HTML นี้ ป้ายกำกับแรกมีไอคอนการนำทางและชื่อ ส่วนที่ 2 มีไอคอนการดำเนินการ

ถัดไป ให้เพิ่มเมธอด 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>
  );
}

นักพัฒนาซอฟต์แวร์จะนำเข้า TopAppBar ไปยังแอปพลิเคชัน React และส่งไอคอนการดำเนินการไปยังองค์ประกอบ TopAppBar ดูตัวอย่างโค้ดเริ่มต้น TopAppBar ได้ใน App.js

ไม่มีเมธอด getMergedStyles ซึ่งใช้ในเมธอด render โปรดเพิ่มเมธอด JavaScript ต่อไปนี้ลงในคลาส TopAppBar:

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

this.classes ก็หายไปจากเมธอด render เช่นกัน แต่จะกล่าวถึงในส่วนต่อไป นอกจากเมธอด Getter ที่ขาดหายไป this.classes แล้ว ยังมี TopAppBar บางอย่างที่คุณต้องติดตั้งใช้งานก่อนที่แถบแอปด้านบนจะแสดงได้อย่างถูกต้อง

ส่วนต่างๆ ของคอมโพเนนต์ React ที่ยังคงขาดหายไปจากแถบแอปด้านบน ได้แก่

  • รากฐานเริ่มต้น
  • วิธีการของอะแดปเตอร์ที่จะส่งผ่านไปยังฐานราก
  • มาร์กอัป JSX
  • การจัดการตัวแปร (คงที่ สั้น ยุบเสมอ โดดเด่น)

วิธีการ

  1. ใช้เมธอดอะแดปเตอร์
  2. เริ่มต้นพื้นฐานใน componentDidMount
  3. เรียกเมธอด Foundation.destroy ใน componentWillUnmount
  4. สร้างการจัดการตัวแปรผ่านเมธอด Getter ที่รวมชื่อคลาสที่เหมาะสมเข้าด้วยกัน

4. ใช้เมธอดของอะแดปเตอร์

คอมโพเนนต์ JS TopAppBar ที่ไม่ใช่เฟรมเวิร์กจะใช้เมธอดของอะแดปเตอร์ต่อไปนี้ (ดูรายละเอียดได้ที่นี่)

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

เนื่องจาก React มีเหตุการณ์สังเคราะห์ รวมถึงแนวทางปฏิบัติและรูปแบบการเขียนโค้ดที่ดีที่สุดที่แตกต่างกัน จึงต้องนำเมธอดของอะแดปเตอร์มาใช้ใหม่

เมธอดตัวรับอะแดปเตอร์

ในไฟล์ 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 ยังต้องเลื่อนไปที่ DOM ดั้งเดิมเนื่องจากเป็นฟังก์ชันในออบเจ็กต์ window ซึ่งไม่ได้อยู่ใน API ของ React การติดตั้งใช้งานอะแดปเตอร์จะแตกต่างกันไปในแต่ละเฟรมเวิร์ก

คุณอาจสังเกตเห็นว่า this.setStyle หายไป ซึ่งเรียกใช้โดยเมธอด get adapter ในไฟล์ 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 สำหรับจัดการชั้นเรียน หากต้องการเลียนแบบเมธอดเพิ่ม/นำคลาส CSS ของ JavaScript แบบเนทีฟออก ให้เพิ่มตัวแปรสถานะ classList โค้ดใน TopAppBar ที่โต้ตอบกับคลาส CSS มี 3 รายการ ได้แก่

  1. คอมโพเนนต์ <TopAppBar /> ผ่านพร็อพเพอร์ตี้ className
  2. วิธีของอะแดปเตอร์ผ่าน addClass หรือ removeClass
  3. ฮาร์ดโค้ดภายในคอมโพเนนต์รีแอ็กชัน <TopAppBar />

ขั้นแรก ให้เพิ่มการนำเข้าต่อไปนี้ที่ด้านบนของ 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 ได้ นักพัฒนาแอปจะโต้ตอบกับ TopAppBar API ได้โดยไม่ต้องกังวลเกี่ยวกับรายละเอียดการใช้งานคลาส CSS

คุณใช้งานอะแดปเตอร์สำเร็จแล้ว ส่วนถัดไปจะแนะนำวิธีการเริ่มต้นรากฐาน

การติดตั้งและยกเลิกการต่อเชื่อมคอมโพเนนต์

การสร้างอินสแตนซ์พื้นฐานจะเกิดขึ้นในเมธอด componentDidMount

ก่อนอื่น ให้นำเข้ารากฐานของ MDC Top App Bar โดยเพิ่มการนำเข้าต่อไปนี้หลังการนำเข้าที่มีอยู่ใน TopAppBar.js

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

จากนั้น เพิ่มโค้ด JavaScript ต่อไปนี้ลงในคลาส 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();
  }
 
  ... 

}

วิธีการเขียนโค้ด React ที่ดีวิธีหนึ่งคือการกำหนด propTypes และ defaultProps เพิ่มการนำเข้าต่อไปนี้หลังจากการนำเข้าที่มีอยู่ใน TopAppBar.js

import PropTypes from 'prop-types';

จากนั้นเพิ่มโค้ดต่อไปนี้ที่ด้านล่างของ TopAppBar.js (หลังคลาสคอมโพเนนต์):

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

คุณใช้งานคอมโพเนนต์ Top App Bar React สำเร็จแล้ว ถ้าคุณไปที่ http://localhost:8080 คุณจะสามารถเล่นกับหน้าการสาธิตได้ หน้าสาธิตจะทำงานเหมือนกับหน้าสาธิตของ MRC Web หน้าการสาธิตควรมีลักษณะดังนี้

3d983b98c2092e7a.png

6. สรุป

ในบทแนะนำนี้ เราได้กล่าวถึงวิธีรวมพื้นฐานของ MDC Web เพื่อใช้ในแอปพลิเคชัน React มีไลบรารีบางรายการใน GitHub และ npm ที่รวมคอมโพเนนต์เว็บ MDC ตามที่อธิบายไว้ในการผสานรวมกับเฟรมเวิร์ก เราขอแนะนำให้คุณใช้รายการที่มีอยู่ที่นี่ รายการนี้ยังมีเฟรมเวิร์กอื่นๆ นอกเหนือจาก React เช่น Angular และ Vue

บทแนะนำนี้จะไฮไลต์การตัดสินใจของเราในการแยกโค้ดเว็บ MDC ออกเป็น 3 ส่วน ได้แก่ พื้นฐาน อะแดปเตอร์ และคอมโพเนนต์ สถาปัตยกรรมนี้ช่วยให้คอมโพเนนต์แชร์โค้ดทั่วไปได้ในขณะที่ทำงานกับเฟรมเวิร์กทั้งหมด ขอขอบคุณที่ลองใช้ Material Components React และดูไลบรารีใหม่ของเรา MDC React เราหวังว่าคุณจะชอบ Codelab นี้

ฉันทำ Codelab นี้เสร็จได้ โดยใช้เวลาและลงแรงพอสมควร

เห็นด้วยอย่างยิ่ง เห็นด้วย เฉยๆ ไม่เห็นด้วย ไม่เห็นด้วยอย่างยิ่ง

ฉันต้องการใช้คอมโพเนนต์เนื้อหาต่อไปในอนาคต

เห็นด้วยอย่างยิ่ง เห็นด้วย เฉยๆ ไม่เห็นด้วย ไม่เห็นด้วยอย่างยิ่ง