במאמרים הקודמים למדנו על קומפוננטות. אבל מה הבעיה עם הקומפוננטות האלו? הן די פשוטות ומשמשות להצגת תוכן בלבד בעוד שרוב הקומפוננטות שנרצה ליצור מחייבות איזושהי אינטראקציה וחייבות לאכסן בתוכן איזשהו מידע שמשתנה. המידע יכול להגיע מבחוץ, יכול להגיע מבפנים או מכל מקום אחר, אבל הקומפוננטה חייבת להכיל מידע ולאכסן אותו. זה נקרא state. כאשר קומפוננטות פשוטות המשמשות לתצוגה בלבד נקראות stateless וקומפוננטות שצריכות לשמור בתוכן מידע הן קומפוננטות שמחייבות state. מבהיל? לא ממש. אם אתם יודעים לעבוד עם תכונות, ולמדנו את זה באחד המאמרים הקודמים, תדעו להסתדר עם state. בגדול, מדובר באובייקט שמכיל מידע ואת המידע הזה אפשר לשנות.
בואו ונדגים, הכל יותר פשוט עם דוגמה. נניח ואתם עובדים בחברת פח לשיווק מוצר סייברי כושל. למשל בזק שרוצה להציג קומפוננטה שמציגה את מספר התקפות הסייבר שיש במדינה. מומחי החברה (כלומר מנהל השיווק אחרי שאכטה טובה) בדקו ומצאו שמדובר ב-1200 התקפות. טוב, זה די קל למימוש. ניצור שתי קומפוננטות. אחת CurrentCyberAttackDisplay שעוטפת את המספר ומספקת עיצוב והשניה CurrentCyberAttackCounter שמספקת אך ורק את המספר. הראשונה תכלול את השניה. איך הקוד יראה? כך:
class CurrentCyberAttackDisplay extends React.Component { render() { const bigStyle = { backgroundColor: 'black', borderRadius: 10, color: 'silver', fontSize: '42px', textAlign: 'center', width: 250, }; const smallStyle = { color: 'gold', fontSize: '25px', }; return <div style={bigStyle}> <CurrentCyberAttackCounter /> <div style={smallStyle}>Cyber attacks per day</div> </div> } } class CurrentCyberAttackCounter extends React.Component { render() { const content = <div>1200</div> // This is JSX return content; } } const target = document.getElementById('content'); ReactDOM.render( <CurrentCyberAttackDisplay />, target );
בשלב הזה של המדריכים, אתם אמורים להבין מעולה את הקוד שלעיל,לא הבנתם? זה הזמן לחזור על המאמרים הקודמים.
מה הבעיה עם הקומפוננטה הזו? מגיע מנהל השיווק ואומר שהוא רוצה שהמספר הזה יעלה בכל 20 שניות ב-100, כדי להלחיץ ולהכניס דינמיות וכדי שהלקוח יילחץ ויזמין את מוצר הסייבר סייברי של החברה. איך עושים את זה? אין לי דרך לעשות את זה אלא באמצעות state . כלומר משתנה בקומפוננטת CurrentCyberAttackCounter שיקבל 1200 והרצה של מתודה שתעלה את המשתנה הזה ב-100 בכל 20 שניות. איך עושים את זה?
הצעד הראשון – יצירת state והצגה שלו. הכי פשוט בעולם. בדיוק בשביל זה אנחנו צריכים constructor. לא יודעים מה זה? לקרוא את המאמר של ES6 קלאסים בקלאסה שיש פה. תתעלמו מהשם הגרוע, הוא תולדה של הקופירייטר הגרוע שיש בי.
בתוך הקונסטרקטור שיהיה ב-CurrentCyberAttackCounter אנחנו נכניס 1200 ל-state.
class CurrentCyberAttackCounter extends React.Component { constructor(props) { super(props); this.state = { attackNumber: 1200, }; } render() { const content =
// This is JSX return content; } }
זה… זה די פשוט, נכון? לא שונה מתצוגה. אבל עכשיו יש לנו משתנה שאנחנו יכולים לפעול עליו. איך? נכתוב מתודה פשוטה בשם timerTick שמעלה את this.state.attackNumber ב-10 בדיוק.
class CurrentCyberAttackCounter extends React.Component { constructor(props) { super(props); this.state = { attackNumber: 1200, }; } timerTick() { this.setState({attackNumber: this.state.attackNumber + 10}); } render() { const content =
// This is JSX return content; } }
זה נהדר אבל מי בדיוק יקרא למתודה הזו? אם אף אחד לא יפעיל את timerTick היא תשכב לה כאבן שאין לה הופכין. אנחנו צריכים פונקציה שתפעל מייד כשהקומפוננטה מתחילה להתרנדר. בדיוק בשביל זה יש לנו את ה-API של ריאקט. בדיוק כמו render, יש לנו את componentDidMount שרצה אך ורק כשהקומפוננטה מוכנה לפעולה. שם נשים את כל מה שאנחנו רוצים לפעול מייד אחרי שהקומפוננטה עובדת. במקרה שלנו, את הקריאה ל-timerTick. או יותר נכון, נשתמש ב-setInterval כדי לקרוא ל-timerTick בכל שניה (1000 מילישניות). איך זה יראה? בדיוק ככה:
class CurrentCyberAttackCounter extends React.Component { constructor(props) { super(props); this.state = { attackNumber: 1200, }; } timerTick() { this.setState({attackNumber: this.state.attackNumber + 10}); } componentDidMount() { setInterval(this.timerTick, 1000); } render() { const content =
// This is JSX return content; } }
רגע, רגע – יש עוד משהו אחר אחרון. אם אני אריץ את הקוד לעיל הוא לא יעבוד ואקבל שגיאה.
למה? כי כש-timerTick רצה בסקופ של setInterval, היא לא תוכל לגשת ל-this. אנחנו חייבים לעשות לה bind באמצעות:
this.timerTick = this.timerTick.bind(this);
אם אתם מבינים למה, מעולה. אם לא – סימן שאתם צריכים לחזור על סקופינג כי זה מאוד חשוב. במאמר הזה אני כותב על כך. זה חובה להבין למה יש bind כי אנחנו עובדים המון עם סקופינג בריאקט (ובכלל בג׳אווהסקריפט מודרני) וחייבים לדעת את זה אחרת תחטפו שגיאה מימין ומשמאל. הכלל הוא: פונקציה/מתודה שאתם משתמשים בה לא יודעת להגיע למשתנה? יש לכם בעיית סקופינג וצריכים להשתמש ב-bind או ב-call\apply.
והנה, זה הקוד המושלם.
class CurrentCyberAttackDisplay extends React.Component { render() { const bigStyle = { backgroundColor: 'black', borderRadius: 10, color: 'silver', fontSize: '42px', textAlign: 'center', width: 250, }; const smallStyle = { color: 'gold', fontSize: '25px', }; return
} } class CurrentCyberAttackCounter extends React.Component { constructor(props) { super(props); this.state = { attackNumber: 1200, }; this.timerTick = this.timerTick.bind(this); } timerTick() { this.setState({attackNumber: this.state.attackNumber + 10}); } componentDidMount() { setInterval(this.timerTick, 1000); } render() { const content =
// This is JSX return content; } } const target = document.getElementById('content'); ReactDOM.render( , target );
והוא גם יעבוד. נסו ותהנו:
See the Pen React component with state management by Ran Bar-Zik (@barzik) on CodePen.
אז הנה, באמצעות דוגמה אחת הראינו איך עובדים עם stateful components בריאקט. למדנו על constructor שהוא חשוב לאיתחול ה-state ועל componentDidMount שבאמצעותו ניהלנו את ה-state. במאמר הבא נדבר על אירועים בקומפוננטות ריאקט ונעשה דברים קצת יותר אינטראקטיביים. מבטיח.
⚠️אם אהבת את המדריכים על ריאקט – יש ספר מקיף ושלם על ריאקט שכתבתי בשם ללמוד ריאקט בעברית, במסגרת פרויקט עם חברות מובילות ומפתחים אחרים. בספר יש פירוט מקיף יותר על ריאקט ותרגילים רבים ללימוד עצמי.
10 תגובות
חשוב לציין ש setState היא אסינכרונית
הערה חשובה 🙂
לא עדיף, במקום ה-bind המכוער, להשתמש ב-arrow function?
setInterval(() => this.timerTick, 1000);
זו תמיד דילמה שמלווה אותי בכל מדריך. האם להדגים כפי שאני כותב בדרך כלל או להעדיף דרכים קצת פחות אלגנטיות אבל יותר קריאות. במקרה הזה העדפתי את הדרך הקריאה יותר.
היי רן,
כמו שמוטי מעליי ציין, פונקצית setState הינה פונקציה אסינכרונית אשר שימוש בה יכול לגרום לתופעות לא רצויות.
כאשר נרצה לעדכן את אחד הStateים על סמך הערך הקודם של אותו הState נעשה זאת על ידי העברה של פונקצית עדכון לתוך setState אשר מקבלת את הState ה-"ישן" ומחזירה אובייקט אשר מכיל את השינוי.
כך לדוגמה נוכל לכתוב את המתודה timerTick כך:
this.setState((prevState) => ({attackNumber: prevState.attackNumber + 10}))
בצורה זאת נוכל להימנע מעדכון לא נכון של הState
מצרף קישור מהדוקיומנטציה הרשמית של ריאקט:
https://reactjs.org/docs/state-and-lifecycle.html
וכמובן מדריך מעולה! 🙂
משום מה התגובה הקודמת שלי לא נקלטה…
מצרף קישור לדוקיומנטציה הרשמית של ריאקט שמסביר על הנושא:
https://reactjs.org/docs/state-and-lifecycle.html
ייתכן והקוד לא נתמך יותר?
אני מנסה לכתוב משהו דומה מאוד ב – codepen וגם להעתיק את הדוגמה הסופית אבל נתקלת בשגיאות
אשמח לעזרה
חחחחחח הרגת אותי מצחוק שפתאום באמצע המדריך המקצועי הזה אני קולט לכלוך שלך על בזק..
חוץ מזה תודה על המדריכים יא מלך, סידר אותי הרבה!