MDC-112 Web: שילוב של MDC עם מסגרות אינטרנט (Web Frameworks)

1. מבוא

logo_components_color_2x_web_96dp.png

‏Material Components‏ (MDC) עוזרים למפתחים להטמיע את Material Design. MDC נוצר על ידי צוות של מהנדסים ומעצבי חוויית המשתמש ב-Google, שכולל עשרות רכיבים יפים ופונקציונליים של ממשק המשתמש. זמין ל-Android, ל-iOS, לאינטרנט ול-Flutter.material.io/develop

MDC Web תוכנן כך שאפשר לשלב אותו בכל מסגרת חזית, תוך שמירה על עקרונות העיצוב של Material Design. בקודלאב הבא תלמדו איך ליצור רכיב React שמשתמש ב-MDC Web כבסיס. אפשר להחיל את העקרונות שלמדתם בקודלאב הזה על כל מסגרת של JavaScript.

איך נוצר MDC Web

שכבת JavaScript של MDC Web מורכבת משלוש כיתות לכל רכיב: Component,‏ Foundation ו-Adapter. התבנית הזו מעניקה ל-MDC Web גמישות לשילוב עם מסגרות חזית.

הבסיס מכיל את הלוגיקה העסקית שמטמיעה את העיצוב החדשני. ב-Foundation אין הפניה לרכיבי HTML. כך אנחנו יכולים להעביר את הלוגיקה של אינטראקציית ה-HTML למתאם. ל-Foundation יש מתאם.

המתאם הוא ממשק. הבסיס מפנה לממשק המתאם כדי להטמיע לוגיקה עסקית של Material Design. אפשר להטמיע את המתאם במסגרות שונות, כמו Angular או React. הטמעה של מתאם יוצרת אינטראקציה עם מבנה ה-DOM.

לרכיב יש בסיס, והתפקיד שלו הוא

  1. מטמיעים את המתאם באמצעות JavaScript ללא מסגרת, וגם
  2. לספק שיטות ציבוריות שמעבירות שיטות ל-Foundation.

מה MDC Web מספק

כל חבילת MDC Web כוללת רכיב, בסיס ומתאם. כדי ליצור רכיב, צריך להעביר את הרכיב הבסיסי (root) ל-method של ה-constructor של הרכיב. הרכיב מיישם מתאם, שמקיים אינטראקציה עם רכיבי ה-DOM וה-HTML. לאחר מכן הרכיב מייצר את ה-Foundation, שקוראת ל-methods של מתאם.

כדי לשלב את MDC Web במסגרת, צריך ליצור רכיב משלכם בשפה או בסנטקס של המסגרת הזו. הרכיב Component של המסגרת מטמיע את Adapter של MDC Web ומשתמש ב-Foundation של MDC Web.

מה תפַתחו

בשיעור הזה תלמדו איך ליצור מתאם מותאם אישית כדי להשתמש בלוגיקה של Foundation כדי ליצור רכיב React של Material Design. הוא מכיל את הנושאים המתקדמים שמפורטים בקטע שילוב במסגרות. ב-codelab הזה נעשה שימוש ב-React כמסגרת לדוגמה, אבל אפשר ליישם את הגישה הזו בכל מסגרת אחרת.

בקודלאב הזה תלמדו ליצור את סרגל האפליקציות העליון, וליצור מחדש את דף הדגמה של סרגל האפליקציות העליון. הפריסה של דף ההדגמה כבר מוגדרת, כך שתוכלו להתחיל לעבוד על סרגל האפליקציות העליון. סרגל האפליקציות העליון יכלול:

  • סמל הניווט
  • פעולות לביצוע
  • יש 4 וריאנטים זמינים: קצר, תמיד מכווץ, קבוע ובולט

מה צריך:

  • גרסה עדכנית של Node.js (שכוללת את npm, מנהל חבילות של JavaScript)
  • הקוד לדוגמה (להורדה בשלב הבא)
  • ידע בסיסי ב-HTML,‏ CSS,‏ JavaScript ו-React

מה מידת הניסיון שלך בפיתוח אינטרנט?

מתחילים בינוניים מומחים

2. הגדרה של סביבת הפיתוח

הורדת האפליקציה לתחילת הדרך ב-Codelab

אפליקציית ההתחלה נמצאת בספרייה material-components-web-codelabs-master/mdc-112/starter.

...או לשכפל אותו מ-GitHub

כדי להעתיק (clone) את סדנת הקוד הזו מ-GitHub, מריצים את הפקודות הבאות:

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

התקנת יחסי התלות בפרויקט

בספריית ה-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 ובודקים את השיטה 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, שהוא כיתה Component רגילה של React עם שיטת 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 של הרכיב. רכיב Top App Bar ייצור תג <header /> ויורכב משני חלקים עיקריים:

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

יש שני רכיבי קטע בקוד ה-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>
  );
}

מפתחים מייבאים את TopAppBar לאפליקציית React שלהם ומעבירים את סמלי הפעולות לרכיב TopAppBar. אפשר לראות קוד לדוגמה לאינטוליזציה של TopAppBar ב-App.js.

חסרה השיטה getMergedStyles, שנעשה בה שימוש ב-method 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 שצריך להטמיע כדי שאפשר יהיה להציג את Top App Bar בצורה נכונה.

החלקים של רכיב React שעדיין חסרים בסרגל העליון של האפליקציה הם:

  • בסיס שהופעל
  • שיטות של מתאמים להעברה ל-Foundation
  • תגי עיצוב של JSX
  • ניהול וריאנטים (קבוע, קצר, תמיד מכווץ, בולט)

הגישה

  1. מטמיעים את השיטות של Adapter.
  2. מפעילים את הבסיס ב-componentDidMount.
  3. קוראים ל-method‏ Foundation.destroy ב-componentWillUnmount.
  4. מגדירים ניהול של וריאנטים באמצעות שיטת getter שמשלבת שמות של כיתות מתאימות.

4. הטמעת שיטות של מתאם

הרכיב TopAppBar של JS ללא מסגרת מיישם את השיטות הבאות של מתאם (רשימת השיטות מפורטת כאן):

  • 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 לניהול כיתות. כדי לחקות את שיטות ההוספה/הסרה של JavaScript ליצירת סוגים של CSS, מוסיפים את משתנה המצב classList. יש שלושה קטעי קוד ב-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. מפתחים יכולים לקיים אינטראקציה עם ה-API של TopAppBar בלי לדאוג לפרטים של ההטמעה של כיתות 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 של Top App Bar. אם עוברים אל http://localhost:8080, אפשר לשחק עם דף הדמו. דף הדגמה יפעל כמו דף הדגמה של MDC Web. דף הדגמה אמור להיראות כך:

3d983b98c2092e7a.png

6. סיכום

במדריך הזה הראינו איך לעטוף את הבסיס של MDC Web לשימוש באפליקציית React. יש כמה ספריות ב-GitHub וב-npm שמארזות את MDC Web Components, כפי שמתואר בקטע שילוב ב-Frameworks. מומלץ להשתמש ברשימה שמופיעה כאן. הרשימה הזו כוללת גם מסגרות אחרות מלבד React, כמו Angular ו-Vue.

במדריך הזה נסביר על ההחלטה שלנו לפצל את הקוד של MDC Web ל-3 חלקים: Foundation,‏ Adapter ו-Component. הארכיטקטורה הזו מאפשרת לרכיבים לשתף קוד משותף תוך כדי עבודה עם כל המסגרות. תודה שניסית את Material Components React. מומלץ לבדוק את הספרייה החדשה שלנו, MDC React. אנחנו מקווים שנהנית מה-Codelab הזה.

הצלחתי להשלים את הקודלהב הזה בזמן ובמאמץ סבירים

נכון מאוד נכון ניטרלי לא נכון לא נכון בכלל

אני רוצה להמשיך להשתמש ב-Material Components בעתיד

נכון מאוד נכון ניטרלי לא נכון לא נכון בכלל