תגובה לקלט עם state
React מספקת דרך הצהרתית לתפעל את ממשק המשתמש. במקום לתפעל חלקים בודדים של ממשק את המשתמש ממש, מתאר את הstate, אתה יכול להיות הרכיב שלך ומחליף בתגובה לקלט המשתמש. זה דומה לאופן שבו מעצבים על ממשק המשתמש.
You will learn
- איך תכנות UI הצהרתי שונה מתכנות UI הכרחי
- איך למנות את הstates הוויזואליים השונים שבהם הרכיב שלך יכול להיות
- איך להפעיל את השינויים בין הstates החזותיים השונים מקוד
איך ממשק משתמש הצהרתי משתווה לציווי
כשאתה מעצב אינטראקציות של ממשק משתמש, אתה כנראה חושב על איך ממשק המשתמש משתנה בתגובה לפעולות המשתמש. שקול טופס המאפשר למשתמש לשלוח תשובה:
- כשאתה מקליד משהו בטופס, הלחצן “שלח” הופך לזמין.
- כאשר אתה לוחץ על “שלח”, גם הטופס וגם הכפתור ** הופכים מושבתים,** ומופיע ספינר .
- אם בקשת הרשת מצליחה, הטופס מוסתר, והודעת “תודה” תופיע.
- אם בקשת הרשת נכשלת, הודעת שגיאה תופיע והטופס מופעל שוב.
בתכנות חובה, האמור לעיל מתאים ישירות לאופן שבו אתה מיישם אינטראקציה. אתה צריך לכתוב את ההוראות המדויקות כדי לתפעל את ממשק המשתמש בהתאם למה שקרה זה עתה. הנה עוד דרך לחשוב על זה: דמיינו לעצמכם נוסעים ליד מישהו במכונית ואומרים לו סבבה לאן ללכת.

Illustrated by Rachel Lee Nabors
הם לא יודעים לאן אתה רוצה, הם פשוט פועלים לפי הפקודות שלך. (ואם אתה טועה בהוראות, אתה מגיע למקום הנכון!) זה נקרא חוויתי כי אתה צריך “לפקודה” על כל אלמנט, מהספינר ועד הכפתור, להגיד למחשב איך לעדכן את ה-UI.
בדוגמה זו של תכנות UI חיוני, הטופס בנוי ללא תגובה. הוא משתמש רק בדפדפן DOM:
async function handleFormSubmit(e) { e.preventDefault(); disable(textarea); disable(button); show(loadingMessage); hide(errorMessage); try { await submitForm(textarea.value); show(successMessage); hide(form); } catch (err) { show(errorMessage); errorMessage.textContent = err.message; } finally { hide(loadingMessage); enable(textarea); enable(button); } } function handleTextareaChange() { if (textarea.value.length === 0) { disable(button); } else { enable(button); } } function hide(el) { el.style.display = 'none'; } function show(el) { el.style.display = ''; } function enable(el) { el.disabled = false; } function disable(el) { el.disabled = true; } function submitForm(answer) { // Pretend it's hitting the network. return new Promise((resolve, reject) => { setTimeout(() => { if (answer.toLowerCase() === 'istanbul') { resolve(); } else { reject(new Error('Good guess but a wrong answer. Try again!')); } }, 1500); }); } let form = document.getElementById('form'); let textarea = document.getElementById('textarea'); let button = document.getElementById('button'); let loadingMessage = document.getElementById('loading'); let errorMessage = document.getElementById('error'); let successMessage = document.getElementById('success'); form.onsubmit = handleFormSubmit; textarea.oninput = handleTextareaChange;
מניפולציה של ממשק המשתמש פועלת באופן הכרחי מספיק טוב עבור דוגמאות בודדות, אך היא הופכת קשה יותר לניהול במערכות מורכבות יותר. דמיינו לעצמכם עדכון של דף מלא בצורות שונות כמו זה. הוספת רכיב ממשק משתמש חדש או אינטראקציה חדשה תדרוש בדיקה קפדנית של כל הקוד הקיים כדי לוודא שלא הצגת באג (לדוגמה, שכחת להציג או להסתיר משהו).
תגיב נבנה כדי לפתור בעיה זו.
ב-React, אינך מבצע מניפולציה ישירה של ממשק משתמש - כלומר אינך מפעיל, משבית, מציג מסתיר רכיבים יחיד. במקום זאת, אתה מצהיר מה אתה רוצה להציג, ו-React מגלה כיצד לעדכן את ממשק המשתמש. תחשוב להיכנס למונית ולהנהג לאן אתה רוצה להגיד לו בדיוק לא לפנות. זה התפקיד של הנהג להביא אותך לשם, אולי הם אפילו מכירים קיצורי דרך שלא שקלת!

Illustrated by Rachel Lee Nabors
חושבים על ממשק משתמש הצהרתי
ראית איך ליישם טופס באופן אישי החי למעלה. כדי להבין טוב יותר איך לחשוב ב-React, תעבור על היישום מחדש של ממשק המשתמש הזה ב-React להלן:
- זהה את הstates החזותיים השונים של הרכיב שלך
- קבע מה גורם לשינויי מצב אלה
- ** להציג** את הstate בזיכרון באמצעות ‘useState’
- הסר כל משתני מצב שאינם חיוניים
- חבר את מטפלי האירועים כדי להגדיר את הstate
שלב 1: זה את הstates החזותיים של הרכיב שלך
במדעי המחשב, אתה יכול לשמוע על “מכונת state” ישת באחת מכמה “מצבים”. אם אתה עובד עם מעצב, אפשר שראית מוקאפים עבור “מצבים חזותיים” שונים. תגובה עומדת בצומת של עיצוב ומדעי המחשב, כך ששני הרעיונות הללו הם מקורות השראה.
ראשית, עליך לדמיין את כל ה”מצבים” השונים של ממשק המשתמש שהמשתמש עשוי לראות:
- ריק: בטופס יש כפתור “שלח” מושבת.
- הקלדה: לטופס יש כפתור “שלח” מופעל.
- שליחה: הטופס מושבת לחלוטין. ספינר מוצג.
- הצלחה: הודעת “תודה” מוצגת במקום טופס.
- שגיאה: זהה לstate ההקלדה, אבל עם הודעת שגיאה נוספת.
בדיוק כמו מעצב, תרצה “לעגוג” או ליצור “לעג” עבור הstates לפני שתוסיף את היגיון. לדוגמה, הנה לעג רק לחלק את הוויזואלי של הטופס. הדמה הזו נשלטת על ידי props שנקרא סטטוס עם ערך ברירת המחדל של 'ריק':
export default function Form({ status = 'empty' }) { if (status === 'success') { return <h1>That's right!</h1> } return ( <> <h2>City quiz</h2> <p> In which city is there a billboard that turns air into drinkable water? </p> <form> <textarea /> <br /> <button> Submit </button> </form> </> ) }
אתה יכול לקרוא לprops הזה כל דבר שתרצה, השם לא חשוב. נסה לערוך את status = 'ריק' ל-status = 'הצלחה' כדי לראות את הודעת ההצלחה מופיעה. אתה יכול ללכת מהר על ממשק המשתמש לפני מחבר היגיון. הנה אב טיפוס יותר מושכל של אותו רכיב, שעדיין “נשלט” על ידי props ה’סטטוס’:
export default function Form({ // Try 'submitting', 'error', 'success': status = 'empty' }) { if (status === 'success') { return <h1>That's right!</h1> } return ( <> <h2>City quiz</h2> <p> In which city is there a billboard that turns air into drinkable water? </p> <form> <textarea disabled={ status === 'submitting' } /> <br /> <button disabled={ status === 'empty' || status === 'submitting' }> Submit </button> {status === 'error' && <p className="Error"> Good guess but a wrong answer. Try again! </p> } </form> </> ); }
Deep Dive
אם לרכיב יש הרבה מצבים חזותיים, זה יכול להיות נוח להציג את כולם בעמוד אחד:
import Form from './Form.js'; let statuses = [ 'empty', 'typing', 'submitting', 'success', 'error', ]; export default function App() { return ( <> {statuses.map(status => ( <section key={status}> <h4>Form ({status}):</h4> <Form status={status} /> </section> ))} </> ); }
דפים כמו זה נקראים לעתים קרובות “מדריכי סגנון חיים” או “ספרי סיפורים”.
שלב 2: קבע מה גורם לשינויי הstate האלה
אתה יכול להפעיל עדכוני מצב בתגובה לשני סוגים של קלט:
- תשומות אנושיות, כמו לחיצה על כפתור, הקלדה בשדה, ניווט בקישור.
- כניסות מחשב, כמו תגובת רשת שמגיעה, השלמת פסק זמן, טעינת תמונה.


Illustrated by Rachel Lee Nabors
בשני המקרים, עליך להגדיר משתני מצב כדי לעדכן את המשתמש ממש. עבור הטופס שאתה מפתח, תצטרך לשנות את הstate בתגובה לכמה כניסות שונות:
- שינוי קלט הטקסט (אנושי) אמור להעביר אותו מstate ריק לstate הקלדה או חזרה, תלוי אם תיבת הטקסט ריקה או לא.
- לחיצה על כפתור שלח (אנושית) אמורה להעביר אותו לstate מגיש.
- תגובת רשת מוצלחת (מחשב) אמורה להעביר אותה לstate הצלחה.
- תגובת רשת נכשלה (מחשב) אמורה להעביר אותה לstate שגיאה עם הודעת השגיאה התואמת.
כדי לעזור לדמיין את הזרימה הזו, נסה לצייר כל מצב על נייר כעיגול מסומן, וכל שינוי בין שני מצבים כחץ. אתה יכול לשרטט זרימות רבות בדרך זו ולסדר באגים הרבה לפני היישום.


מצבי טופס
שלב 3: להציג את הstate בזיכרון באמצעות useState
בשלב הבא תעבוד להצגת הstates הוויזואליים של הרכיב שלך בזיכרון עם useState. הפשטות היא הפתח: כל חלק של מצב הוא “חלק נע”, ואתה רוצה כמה שפחות “חלקים זזים” ככל האפשר. יותר מורכבות מובילה ליותר באגים!
תתחיל מstate שבהחלט חייבת להיות שם. לדוגמה, תצטרך לאחסן את ה’תשובה’ עבור הקלט, ואת ה’שגיאה’ (אם היא קיימת) כדי לאחסן את השגיאה האחרונה:
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);לאחר מכן, תזדקק למשתנה מצב המייצג את אחד מstates החזותיים שברצונך להציג. בדרך כלל יש יותר מדרך אחת לייצג את זה בזיכרון, אז תצטרך להתנסות עם זה.
אם אתה מתקשה לחשוב על הדרך הטובה ביותר מיד, התחל בהוספת מספיק מצבים שאתה בהחלט בטוח שכל הstates החזותיים האפשריים מכוסים:
const [isEmpty, setIsEmpty] = useState(true);
const [isTyping, setIsTyping] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);סביר להניח שהרעיון הראשון שלך לא יהיה הטוב ביותר, אבל זה בסדר - מצב החזרה הוא חלק מהתהליך!
שלב 4: הסר משתני מצב חיוניים
אתה רוצה למנוע כפילות בתוכן הstate אז אתה עוקב רק אחר מה שחשוב. השקעת זמן קצר בשינוי מבנה הstate שלך יקל על ההבנה של הרכיבים שלך, יפחית כפילות ויימנע ממשמעויות לא מכוונות. המטרה שלך היא למנוע את המקרים שבהם הstate בזיכרון אינו מייצג שום ממשק משתמש חוקי שהיית רוצה שמשתמש יראה. (לדוגמה, לעולם לא תרצה להציג הודעת שגיאה ולהשבית את הקלט בו-זמנית, או שהמשתמש לא יוכל לתקן את השגיאה!)
הנה כמה שאלות שאתה יכול לשאול לגבי משתני הstate שלך:
- האם מצב זה גורם לפרדוקס? לדוגמה,
isTypingו-issubmittingלא יכולים להיות שניהםנכונים. פרדוקס פירושו בדרך כלל שstate אינה מוגבלת מספיק. יש ארבעה שילובים אפשריים של שני בוליאנים, אך רק שלושה תואמים מצבים תקפים. כדי להוציא את הstate ה”בלתי אפשרי”, אתה יכול לשלב אותם ל’סטטוס’ שחייב להיות משלו ערכים אחדים: ”הקלדה’, ''שליחת'או ”הצלחה’`. - האם אותו מידע זמין כבר בשינוי מצב אחר? פרדוקס נוסף:
isEmptyו-isTypingלא יכולים להיותtrueבו-זמנית. על ידי הפיכתם למשתני מצב נפרדים, אתה מסתכן שהם יצאו מסנכרון ויגרמו לבאגים. למרבה המזל, אתה יכול להוציא אתisEmptyובמקום זאת לסמן אתanswer.length === 0. - האם אתה יכול לקבל אותו מידע מהיפוך של שינוי מצב אחר? אין צורך ב-‘isError’ כי אתה יכול לבדוק את ‘error !== null’ במקום זאת.
לאחר הניקוי הזה, נשארו לך 3 (ירידה מ-7!) חיוניים משתני מצב:
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'אתה יודע שהם חיוניים, כי אתה לא יכול להסיר אף אחד מהם מבלי לשבור את הפונקציונליות.
Deep Dive
שלושת המשתנים הללו הם ייצוג מספיק טוב של מצב הצורה הזו. עם זאת, יש לי ממש כמה מצבי. לדוגמה, שגיאה אינה אפס אינה הגיונית כאשר סטטוס הוא הצלחה. כדי לדגמן את הstate בצורה מדויקת יותר, אתה לחלץ אותו לתוך reducer. reducer לך לאחד מצב שני מרובים לאובייקט אחד ולאחד את כל ההיגיון הקשור!
שלב 5: חברו את מטפלי האירועים לעדכון מצב
לבסוף, צור מטפלי אירועים שמעדכנים את הstate. להלן הטופס הסופי, עם כל מטפלי האירועים מחוברים:
import { useState } from 'react'; export default function Form() { const [answer, setAnswer] = useState(''); const [error, setError] = useState(null); const [status, setStatus] = useState('typing'); if (status === 'success') { return <h1>That's right!</h1> } async function handleSubmit(e) { e.preventDefault(); setStatus('submitting'); try { await submitForm(answer); setStatus('success'); } catch (err) { setStatus('typing'); setError(err); } } function handleTextareaChange(e) { setAnswer(e.target.value); } return ( <> <h2>City quiz</h2> <p> In which city is there a billboard that turns air into drinkable water? </p> <form onSubmit={handleSubmit}> <textarea value={answer} onChange={handleTextareaChange} disabled={status === 'submitting'} /> <br /> <button disabled={ answer.length === 0 || status === 'submitting' }> Submit </button> {error !== null && <p className="Error"> {error.message} </p> } </form> </> ); } function submitForm(answer) { // Pretend it's hitting the network. return new Promise((resolve, reject) => { setTimeout(() => { let shouldError = answer.toLowerCase() !== 'lima' if (shouldError) { reject(new Error('Good guess but a wrong answer. Try again!')); } else { resolve(); } }, 1500); }); }
למרות שהקוד הזה ארוך יותר מהדוגמה המקורית של הציווי, הוא הרבה פחות שביר. ביטוי כל האינטראקציות כשינויי מצב מאפשר לך להציג מאוחר יותר מצבים חזותיים חדשים מבלי לשבור את הstates הקיימים. זה גם מאפשר לך לשנות את מה שצריך להיות מוצג בכל מצב מבלי לשנות את ההיגיון של האינטראקציה עצמה.
Recap
- תכנות הצהרתי פירושו תיאור ממשק המשתמש עבור כל מצב ויזואלי במקום ניהול מיקרו של ממשק המשתמש (הכרח).
- בעת פיתוח רכיב:
- זהה את כל הstates החזותיים שלו.
- קבע את הטריגרים האנושיים והמחשבים לשינויי מצב.
- דגם את הstate באמצעות ‘useState’.
- הסר מצב לא חיוני כדי למנוע באגים ופרדוקסים.
- חבר את מטפלי האירועים לstate מוגדר.
Challenge 1 of 3: הוסף והסר מחלקת CSS
הפוך את זה כך שלחיצה על התמונה תסיר את מחלקת ה-CSS background--active מהמחלקה החיצונית <div>, אך מוסיפה את המחלקה picture--active למחלקה <img>. לחיצה נוספת על הרקע אמורה לשחזר את מחלקות ה-CSS המקוריות.
מבחינה ויזואלית, אתה צריך לצפות שלחיצה על התמונה תסיר את הרקע הסגול ותדגיש את גבול התמונה. לחיצה מחוץ לתמונה מדגישה את הרקע, אך מסירה את סימון גבול התמונה.
export default function Picture() { return ( <div className="background background--active"> <img className="picture" alt="Rainbow houses in Kampung Pelangi, Indonesia" src="https://i.imgur.com/5qwVYb1.jpeg" /> </div> ); }