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

1. บทนำ

logo_components_color_2x_web_96dp.png

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

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

วิธีสร้าง MDC Web

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

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

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

คอมโพเนนต์มีรากฐาน และบทบาทของรากฐานคือ

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

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

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

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

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

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

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

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

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

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

คุณจะให้คะแนนระดับประสบการณ์ด้านการพัฒนาเว็บเท่าใด

ผู้ฝึกหัด ระดับกลาง ผู้ชำนาญ

2. ตั้งค่าสภาพแวดล้อมสำหรับนักพัฒนาซอฟต์แวร์

ดาวน์โหลดแอป Codelab เริ่มต้น

แอปเริ่มต้นอยู่ในไดเรกทอรี material-components-web-codelabs-master/mdc-112/starter

...หรือจะโคลนจาก GitHub ก็ได้

หากต้องการโคลนโค้ดแล็บนี้จาก 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

สำเร็จ! โค้ดเริ่มต้นสําหรับหน้าสาธิต React ของแถบแอปด้านบนควรทํางานในเบราว์เซอร์ คุณควรเห็นข้อความ lorem ipsum เต็มหน้าจอ กล่องการควบคุม (ด้านขวาล่าง) และแถบแอปด้านบนที่ยังไม่เสร็จสมบูรณ์

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. ใช้เมธอดของอะแดปเตอร์

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

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

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

เมธอด 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 ยังต้องเลื่อนไปที่ 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 โค้ด 3 รายการใน TopAppBar ที่โต้ตอบกับคลาส CSS มีดังนี้

  1. <TopAppBar /> ผ่านพร็อพเพอร์ตี้ className
  2. เมธอด Adapter ผ่าน 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 ได้ นักพัฒนาแอปสามารถโต้ตอบกับ TopAppBar API ได้โดยไม่ต้องกังวลเกี่ยวกับรายละเอียดการใช้งานคลาส CSS

คุณได้ติดตั้งใช้งานอะแดปเตอร์เรียบร้อยแล้ว ส่วนถัดไปจะแนะนำการสร้างอินสแตนซ์ Foundation

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

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

ก่อนอื่น ให้นําเข้ารากฐานของแถบแอปด้านบน MDC โดยเพิ่มการนําเข้าต่อไปนี้หลังจากการนําเข้าที่มีอยู่ใน 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,
};

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

3d983b98c2092e7a.png

6. สรุป

ในบทแนะนํานี้ เราจะอธิบายวิธีรวม Foundation ของ MDC Web เพื่อใช้ในแอปพลิเคชัน React มีไลบรารี 2-3 รายการใน Github และ npm ที่รวม MDC Web Components ไว้ด้วยกันตามที่อธิบายไว้ในการผสานรวมกับเฟรมเวิร์ก เราขอแนะนำให้คุณใช้รายการที่ระบุไว้ที่นี่ รายการนี้ยังมีเฟรมเวิร์กอื่นๆ นอกเหนือจาก React เช่น Angular และ Vue

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

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

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

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

เห็นด้วยอย่างยิ่ง เห็นด้วย เป็นกลาง ไม่เห็นด้วย ไม่เห็นด้วยอย่างยิ่ง