ריאקט: צעדים ראשונים

פוסט זה כולל טיפ קצר בנושא פיתוח Front End. אם אתם רוצים ללמוד יותר לעומק על פיתוח Front End מהבסיס ועד הנושאים המתקדמים תשמחו לשמוע שבניתי קורס וידאו מקיף בנושא זה הכולל מעל 50 שיעורי וידאו והמון תרגול מעשי.
למידע נוסף והצטרפות לקורס בקרו בדף קורס Front End באתר.
 

את הפוסט הבא כתבתי לא מזמן עבור אתר webmaster.org.il.

ריאקט הינה ספריית צד-לקוח לבניית ממשקי Web מורכבים. הספריה פותחה בפייסבוק ונמצאת בשימוש במערכות שלהם ובעיקר באתר Instagram. רשימת האתרים המשתמשים בריאקט כוללת את Wix, Khan Academy, AddThis, Flipboard ורבים נוספים. ריאקט מציעה גישה חדשנית ביחס לספריות MVC מסורתיות בצד הלקוח ולכן זוכה להתעניינות רבה מצד מפתחים.

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

1. פיתוח מונחה פקדים

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

תיבת החיפוש היא פקד, רשימת הפריטים שתחתיה היא פקד נוסף. זה בסדר ואף רצוי שתהיה אינטרקציה בין הפקדים, כלומר טקסט שאני רושם בתיבת החיפוש משפיע באופן מיידי על פקד רשימת הפריטים. 
המסך כולו אף הוא פקד, ולחיצה על אחד הפריטים ברשימה תעביר אותי למסך אחר — שוב פקד אחר. הפקד כולל גם את הייצוג הויזואלי של הרכיב (כלומר קוד תבנית ה HTML) וגם את הפעולות שהוא מאפשר. 

כבר מתיאור זה אנו רואים שפקדים בריאקט הינם לרוב מקוננים אחד בתוך השני. ממשק משתמש מלא מאופיין בתור עץ של פקדים המיוצג לרוב במבנה דמוי XML. כך למשל יכול להיראות ייצוג של הפקד בדוגמא:

<Page>
  <Header />
  <FilteredList>
    <Input />
    <Items />
  </FilteredList>
  <Footer />
</Page>

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

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

2. פקד ראשון בריאקט

עד כה השתמשתי במושג פקד לתאר שני מושגים: גם את הדבר עצמו שמופיע על המסך וגם את ״סוג״ הדבר. פקד כפתור לדוגמא יכול להופיע מספר פעמים על המסך, בכל פעם עם טקסט אחר או פעולה אחרת כתגובה ללחיצה. הדבר דומה להבדל בין מחלקה לאובייקט בתכנות מונחה עצמים. בריאקט ההבדל הוא בין Class לבין Element, כאשר הראשון מייצג את מחלקת-הפקד, כלומר סוג הדבר, ואילו השני (Element) מייצג מופע ספציפי של פקד על המסך.    

מחלקת הפקד היא תבנית לייצור פקדים מסוג מסוים. אנו יוצרים מחלקת פקד עם React.createClass ולאחר מכן מציגים את הפקד על המסך באמצעות קריאה ל React.render. כך לדוגמא הקוד הבא יציג פקד שלום עלום על המסך:

הפונקציה React.createClass יצרה את מחלקת הפקד. פונקציה זו מקבלת אובייקט פרמטרים ומחזירה מחלקת פקד מתאימה. הפרמטר היחיד ההכרחי באובייקט הפרמטרים הינו הפונקציה render המחזירה את התבנית.
    
נשים לב שמבנה התבנית אינו JavaScript תקני. זוהי שפת XML הנקראת JSX. מטרתה לאפשר איפיון תבניות בצורה ויזואלית ונוחה, כך שדוגמת ה XML שהצגתי בחלק הקודם למעשה יכולה להיות קוד תבנית תקני בריאקט. קוד JSX הופך לקוד JavaScript תקני בתהליך המרה באמצעות הספריה JSXTransform אותה כללתי בקוד הדוגמא.

יש מספר דרכים להריץ את קוד הדוגמא ולהמיר את קוד ה JSX לקוד JavaScript תקני. תוכנית הדוגמא משתמשת בספריית JavaScript שנקראת JSXTransform. ספריה זו ממירה קטעי JSX לקוד רגיל בתוך הדפדפן ובכך מתאפשרת הרצתו. במערכות אמיתיות לרוב לא נשתמש בשיטה זו ונבחר להמיר את קבצי ה JSX לקוד JavaScript רגיל לפני שליחתו ללקוח באמצעות הכלי:
https://www.npmjs.com/package/react-tools

3. העברת קלטים לפקד

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

שימו לב לשימוש בסוגריים מסולסלים בתוך קוד ה JSX. כתיב זה מאפשר ״בריחה״ מ JSX בחזרה ל JavaScript, כך שמה שייכתב בתבנית הוא תוצאה שערוך הביטוי בתוך הסוגריים המסולסלים. מחר והביטוי הוא משתנה יוצג ערכו.

העברת ערך למשתנה מבוצעת מחוץ למחלקת הפקד בעת יצירת האלמנט בשורה:

React.render(<Greeter name="Ynon" />, document.querySelector('#container'));

 

4. הדגמת הספריה דרך פיתוח קוד למשחק תפוס ת׳אדום

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

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

במקרה שלנו מספר הריבועים בלוח הוא קלט (property). הנקודות ומיקום הריבוע המנצח הם מצב (state). המצב נשמר תחת השדה state של הפקד, כשם שהקלטים נשמרים תחת השדה props. פקד הכולל מצב צריך להגדיר גם מהו המצב ההתחלתי, ולכן הפונקציה הראשונה שנכתוב היא פונקציה מיוחדת שנקראת getInitialState. פונקציה זו נקראת אוטומטית בעת יצירת אלמנט וצירופו לעמוד. הפונקציה מחזירה את הערכים הראשוניים של המצב:

getInitialState: function() {
     return { winner: this.getRandomCell(), score: 0 };
},

כדי להקל את המימוש הפרדתי את פעולת איתור תא אקראי עבור המלבן המנצח לפונקציה נפרדת. הפונקציה תשתמש בקלט count שהגיע מבחוץ ובספריה Underscore כדי להגריל מספר אקראי בטווח. המשתנה this.props.count בתוך פונקציית הפקד מתיחס למספר הריבועים הכולל אותו קיבלנו כקלט. באותו האופן נוכל בהמשך להתיחס ל this.state.winner ו this.state.score כדי לפנות למשתני המצב.
כך נראית הפונקציה:

getRandomCell: function() {
     return _.random(this.props.count - 1);
},

כעת אפשר להתחיל לחשוב על הפעולות שיגרמו לשינוי במצב. במקרה שלנו זה קל, הפעולה היחידה היא לחיצה על ריבוע. נגדיר פונקציה שלישית שתטפל בלחיצה על ריבוע. הלוגיקה יחסית פשוטה— אם אינדקס הריבוע הוא בדיוק האינדקס המנצח יש להוסיף 5 נקודות ולהגריל מנצח חדש, אחרת יש להוריד 5 נקודות.
מאחר והמשתנה score הוא חלק ממצב הפקד, עלינו להשתמש בפונקציה של ריאקט שנקראת setState כדי לעדכן משתנה זה. באופן כללי פקד יכול לעדכן את מצבו בתגובה לאירועים חיצוניים, וכל שינוי מצב יוביל לעדכון התצוגה במצב הפקד החדש.
כך נראה הקוד:

clicked: function(i) {
     if ( i === this.state.winner ) {
          this.setState({
               score: this.state.score + 5,
               winner: this.getRandomCell()
          });
     } else {
          this.setState({
         	  score: this.state.score - 5
   	   });
     }
},

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

עכשיו שכל הלוגיקה כתובה אפשר להמשיך לפונקציית ה render. מאחר ומדובר בלוח מורכב בחרתי לחלק את הקוד לשתי פונקציות, האחת עבור הצגת כל כפתור והשניה עבור הצגת הלוח במלואו.
על פונקציית render דיברנו בסעיפים קודמים. מטרתה להחזיר את ייצוג ה JSX של הפקד. עבור כל כפתור לדוגמא הייצוג מורכב מאלמנט button עם הקלאס cell. אם זהו כפתור הנצחון נוסיף עליו גם את הקלאס winner. 

לחיצה על הכפתור צריכה לקרוא לפונקציית הטיפול בלחיצה שהגדרנו קודם ולהעביר את מזהה הכפתור כפרמטר. כך נראה הקוד:

renderButton: function(i) {
     var cls = "cell ";
      if ( i === this.state.winner ) {
          cls += "winner";
     }
     return <button className={cls} onClick={this.clicked.bind(this,i)}></button>
},

רוב הקוד כאן הוא קוד JavaScript רגיל לגמרי. השימוש בריאקט בא לידי ביטוי רק בשורת הקוד האחרונה בה יצרנו את ייצוג הפקד.

שלוש נקודות חשובות לגבי הקוד:

  1. אין בעייה להשתמש בסוגריים מסולסלים בעת יצירת אלמנט והעברת פרמטרים אליו. פרמטרים שנכתבים בתוך סוגריים מסולסלים יכולים להיות אובייקטי JavaScript רגילים, ולכן הצלחתי להעביר פונקציית JS למאפיין onClick. 
  2. השימוש במאפיין className במקום השם class נובע ממגבלה של השפה. קוד JSX הופך לקוד JavaScript, והמאפיינים הופכים לאובייקט פרמטרים. המילה class היא מילה שמורה ב JavaScript ולכן היה צריך למצוא מילה אחרת.
  3. המאפיין onClick מצפה לקבל פונקציה. הפונקציה bind מחזירה פונקציה חדשה ״הקשורה״ לאובייקט ופרמטרי קריאה מסוימים. כך בעצם אנו מבקשים שבכל פעם שמשתמש לוחץ על כפתור באינדקס מסוים, תקרא הפונקציה clicked עם אותו האינדקס.

פונקציית ה render הראשית משתמשת בפונקציית renderButton כדי להציג את הרשימה המלאה. אנו צריכים לבנות את התבנית בקוד JavaScript ולכן הפונקציה מתחילה בלולאה שבונה את כל הכפתורים ורק לאחר מכן מחזירה את התבנית:

render: function() {
     var btns = [];
     for ( var i=0; i < this.props.count; i++ ) {
          btns.push(this.renderButton(i));
     }

     return (
          <div>
               <p>Score: {this.state.score}</p>
            	<div>
                    {btns}
               </div>
          </div>
     )
}

כך נראה קוד המשחק המלא:

5. סיכום וקריאת המשך

הדבר המדהים בקוד שכתבנו הוא שלא נגענו בכלל בשאלה כיצד להפוך את הלוגיקה, ה Data שלנו, לאלמנטים ב DOM. עובדה זו מאפשרת לכתוב קוד מאוד קצר ונקי, שהרי הקוד מתעסק רק בלוגיקה, ובאמצעות שפת התבניות ריאקט כבר דואגת להמיר את הלוגיקה לאלמנטים ב DOM.

נקודה חשובה שניה שלא הרחבתי עליה בסקירה היא חוסר התלות של הקוד שלנו ב DOM. למרות שנראה כאילו מה שאנחנו מחזירים מפונקציית render הם אלמנטי button, p או div — אלו למעשה אובייקטים של ריאקט. האלמנטים שיכתבו בפועל ב DOM יכולים להיות דומים או שונים למה שביקשנו. דוגמא פשוטה היא הוספת span סביב מספר הנקודות (משהו שקורה אוטומטית), כדי ששינוי מספר הנקודות לא יצריך שינוי של הטקסט המלווה, כלומר כל אלמנט ה p.
הייתרון הגדול בגישה זו הוא האפשרות לבנות את כל מחרוזת ה HTML של הפקד ללא שימוש בדפדפן ובצד השרת. כך גולש שמגיע לאתר שלכם מקבל את העמוד הראשוני כבר מוכן עם התוכן כקובץ HTML רגיל לחלוטין. זה קריטי למי שכותב אתר תוכן שצריך להיות מאונדקס על ידי מנועי חיפוש, וגם משפר את הזמן עד לעליית העמוד.

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