ככל שהאפליקציה שלך גדלה, זה עוזר להיות יותר מכוון לגבי האופן שבו הstate שלך מאורגנת ואיך זורמים בין הרכיבים שלך. מצב מיותר או כפול הוא מקור נפוץ לבאגים. בפרק זה, תלמד כיצד לבנות את הstate שלך היטב, כיצד לשמור על לוגיקה של עדכון הstate שלך ניתנת לתחזוקה וכיצד לשתף מצב בין רכיבים מרוחקים.
In this chapter
מגיבה לקלט עם מצב
עם React, לא תשנה את ממשק משתמש מהקוד בעצם. לדוגמה, לא תכתוב פקודות כמו “השבת את הלחצן”, “הפעל את הלחצן”, “הצג את הודעת ההצלחה” וכו’. במקום זאת, תתאר את ממשק המשתמש שאתה רוצה עבור הstates החזותיים לראות את הרכיב שלך (“מצב התחלתי”, “מצב הקלדה”, “מצב הצלחה”), תפעיל את שינוי הstate בתגובה למשתמש. זה דומה לאופן שבו מעצבים על ממשק משתמש.
להלן טופס חידון שנבנה באמצעות React. שים לב איך הוא משתמש בשינוי ה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); }); }
Ready to learn this topic?
קרא את להגיב לקלט עם מצב כדי ללמוד כיצד לגשת לאינטראקציות עם חשיבה מונעת לפי מצב.
Read Moreבחירת מבנה הstate
מבנה מצב טוב לעשות הבדל בין רכיב שנעים לשינוי וניפוי באגים, כזה שמהווה מקור קבוע לבאגים. העיקרון העיקרי הוא שstate לא צריך להכיל מידע מיותר או משוכפל. אם יש מצב מיותר, קל לשכוח לעדכן אותו ולהציג באגים!
לדוגמה, לטופס זה יש משתנה מצב מיותר ‘מלא שם’:
import { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [fullName, setFullName] = useState(''); function handleFirstNameChange(e) { setFirstName(e.target.value); setFullName(e.target.value + ' ' + lastName); } function handleLastNameChange(e) { setLastName(e.target.value); setFullName(firstName + ' ' + e.target.value); } return ( <> <h2>Let’s check you in</h2> <label> First name:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Last name:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Your ticket will be issued to: <b>{fullName}</b> </p> </> ); }
אתה יכול להסיר אותו ולפשט את הקוד על ידי חישוב ‘שם מלא’ בזמן שהרכיב מעבד:
import { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const fullName = firstName + ' ' + lastName; function handleFirstNameChange(e) { setFirstName(e.target.value); } function handleLastNameChange(e) { setLastName(e.target.value); } return ( <> <h2>Let’s check you in</h2> <label> First name:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Last name:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Your ticket will be issued to: <b>{fullName}</b> </p> </> ); }
זה אולי נראה כמו שינוי קטן, אבל באגים רבים באפליקציות הגיבו מתוקנים בדרך זו.
Ready to learn this topic?
קרא את Choosing the State Structure כדי ללמוד לעצב את צורת הstate כדי למנוע באגים.
Read Moreמצב שיתוף בין רכיבים
לפעמים אתה רוצה שstateם של שני רכיבים ישתנה תמיד ביחד. כדי לעשות זאת, הם יעבירו אותו להורה המשותף הקרוב. זה ידוע בשם “רמת מצב למעלה”, וזה אחד הדברים הנפוצים ביותר שתעשה בכתיבת קוד React.
בדוגמה זו, רק חלונית אחת צריכה להיות פעילה בכל פעם. כדי להשיג זאת, במקום לשמור על הstate הstate הprops עבור ילדיו.
import { useState } from 'react'; export default function Accordion() { const [activeIndex, setActiveIndex] = useState(0); return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title="About" isActive={activeIndex === 0} onShow={() => setActiveIndex(0)} > With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. </Panel> <Panel title="Etymology" isActive={activeIndex === 1} onShow={() => setActiveIndex(1)} > The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. </Panel> </> ); } function Panel({ title, children, isActive, onShow }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={onShow}> Show </button> )} </section> ); }
Ready to learn this topic?
קרא את מצב שיתוף בין רכיבים כדי ללמוד כיצד להעלות מצב ולשמור על רכיבים מסונכרנים.
Read Moreשמירה ואיפוס מצב
כאשר אתה מעבד מחדש רכיב, React צריך להחליט אילו חלקים בעץ (ולעדכן), אילו חלקים לזרוק או ליצור מחדש מאפס. רוב המקרים, ההתנהגות האוטומטית של React עובדת מספיק טוב. כברירת מחדל, תגובה משמר את חלקי העץ מתאים” לעץ הרכיבים שעובד קודם לכן.
עם זאת, לפעמים זה לא מה שאתה רוצה. באפליקציית הצ’אט הזו, הקלדת הודעה ואז החלפת הנמען לא מאפסת את הקלט. זה יכול לגרום למשתמש לשלוח בטעות הודעה לאדם הלא נכון:
import { useState } from 'react'; import Chat from './Chat.js'; import ContactList from './ContactList.js'; export default function Messenger() { const [to, setTo] = useState(contacts[0]); return ( <div> <ContactList contacts={contacts} selectedContact={to} onSelect={contact => setTo(contact)} /> <Chat contact={to} /> </div> ) } const contacts = [ { name: 'Taylor', email: 'taylor@mail.com' }, { name: 'Alice', email: 'alice@mail.com' }, { name: 'Bob', email: 'bob@mail.com' } ];
תגובה מאפשרת לך לעקוף את התנהגות ברירת המחדל, ולהכריח רכיב לאפס את מצבו על ידי העברה לו מפתח אחר, כמו <Chat key={email} />. זה ל-React שאם הנמען שונה, זה צריך להיחשב כרכיב ‘צ’אט’ שונה שצריך ליצור מחדש מאפס עם החדשים (וכניסות כמו ממשק משתמש). עדיין בין הנמענים מאפס את שדה הקלט - למרות שאתה מעבד אותו רכיב.
import { useState } from 'react'; import Chat from './Chat.js'; import ContactList from './ContactList.js'; export default function Messenger() { const [to, setTo] = useState(contacts[0]); return ( <div> <ContactList contacts={contacts} selectedContact={to} onSelect={contact => setTo(contact)} /> <Chat key={to.email} contact={to} /> </div> ) } const contacts = [ { name: 'Taylor', email: 'taylor@mail.com' }, { name: 'Alice', email: 'alice@mail.com' }, { name: 'Bob', email: 'bob@mail.com' } ];
Ready to learn this topic?
קרא את שימור ואיפוס מצב כדי ללמוד את משך החיים של הstate וכיצד לשלוט בו.
Read Moreחילוץ היגיון מצב לתוך מפחית
רכיבים עם עדכוני מצב רבים מהמפוארים על פני רופאי אירועים רבים יכולים להיות מהמם. אלה, אתה יכול לאחד את כל הלוגיקה של עדכון הstate מחוץ לרכיב שלך בפונקציה אחת, הנקראת “מפחית”. המטפלים שלך הופכים תמצי שהם מציינים רק את המשתמש “פעולות”. בתחתית הקובץ, פונקציית המפחית מציינת כיצד הstate צריך להתעדכן בתגובה לכל פעולה!
import { useReducer } from 'react'; import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; export default function TaskApp() { const [tasks, dispatch] = useReducer( tasksReducer, initialTasks ); function handleAddTask(text) { dispatch({ type: 'added', id: nextId++, text: text, }); } function handleChangeTask(task) { dispatch({ type: 'changed', task: task }); } function handleDeleteTask(taskId) { dispatch({ type: 'deleted', id: taskId }); } return ( <> <h1>Prague itinerary</h1> <AddTask onAddTask={handleAddTask} /> <TaskList tasks={tasks} onChangeTask={handleChangeTask} onDeleteTask={handleDeleteTask} /> </> ); } function tasksReducer(tasks, action) { switch (action.type) { case 'added': { return [...tasks, { id: action.id, text: action.text, done: false }]; } case 'changed': { return tasks.map(t => { if (t.id === action.task.id) { return action.task; } else { return t; } }); } case 'deleted': { return tasks.filter(t => t.id !== action.id); } default: { throw Error('Unknown action: ' + action.type); } } } let nextId = 3; const initialTasks = [ { id: 0, text: 'Visit Kafka Museum', done: true }, { id: 1, text: 'Watch a puppet show', done: false }, { id: 2, text: 'Lennon Wall pic', done: false } ];
Ready to learn this topic?
קרא את חילוץ היגיון בstate reducer כדי ללמוד איך לאחד את ההיגיון בפונקציית הרדוקרה.
Read Moreהעברת נתונים עמוקה עם הקשר
בדרך כלל, תעביר מידע לגבי אב רכיב ילד באמצעות props. אבל להעביר props יכולה להיות לא נוחה אם אתה צריך להעביר props דרך רכיבים רבים, או אם רכיבים רבים זקוקים למידע. ההקשר היכול לרכיב האב יצור מידע לזמין לכל רכיב בעץ שמתחתיו - לא משנה כמה הוא עמוק - לא להעביר אותו במפורש דרך props.
כאן, רכיב Heading קובע את רמת הכותרת שלו על ידי “שאילת” הSection הקרוב ביותר לרמתו. כל ‘מדור’ עוקב אחר רמה משלו על ידי שאילת ‘מדור’ האב והוספת אחד אליו. כל ‘מדור’ מספק מידע לכל הרכיבים שמתחתיו להעברת props - הוא עושה זאת באמצעות הקשר.
import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return ( <Section> <Heading>Title</Heading> <Section> <Heading>Heading</Heading> <Heading>Heading</Heading> <Heading>Heading</Heading> <Section> <Heading>Sub-heading</Heading> <Heading>Sub-heading</Heading> <Heading>Sub-heading</Heading> <Section> <Heading>Sub-sub-heading</Heading> <Heading>Sub-sub-heading</Heading> <Heading>Sub-sub-heading</Heading> </Section> </Section> </Section> </Section> ); }
Ready to learn this topic?
קרא את העברת נתונים עמHooks עם ההקשר כדי ללמוד לפעול בהקשר כחלופה לprops המועברים.
Read Moreהגדלה עם מפחית והקשר
מפחית מאפשרים לך לאחד את לוגיקת עדכון הstate של רכיב. ההקשר האפשרי להעביר מידע אחר למטה לרכיבים. אתה יכול להפחית את השלב והקשר יחד כדי לנהל מצב של כמות מורכבת.
בגישה זו, רכיב אב עם מצב מורכב מנהל אותו עם מפחית. רכיבים אחרים בכל מקום עמוק בעץ יכולים לקרוא את המצב באמצעות הקשר. הם יכולים גם לשלוח פעולות לעדכון מצב זה.
import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; import { TasksProvider } from './TasksContext.js'; export default function TaskApp() { return ( <TasksProvider> <h1>Day off in Kyoto</h1> <AddTask /> <TaskList /> </TasksProvider> ); }
Ready to learn this topic?
קרא את הגדלה עם מפחית והקשר כדי ללמוד ניהול מצב מתרחב באפליקציה צומחת.
Read Moreמה הלאה?
עבור אל מגיב לקלט עם מצב כדי להתחיל לקרוא פרק זה עמוד אחר עמוד!
או, אם אתה כבר מכיר את הנושאים האלה, למה שלא תקרא על Escape Hatches?