state: הזיכרון של קומפוננטה

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

You will learn

  • כיצד להוסיף משתנה מצב עם ה- useState
  • איזה צמד ערכים ה-‘useState’ מחזיר
  • כיצד להוסיף יותר ממשתנה מצב אחד
  • מדוע state נקראת מקומית

כאשר משתנה רגיל אינו מספיק

הנה רכיב שמציג תמונה של פיסול. לחיצה על כפתור “הבא” אמורה להציג את הפסל הבא על ידי שינוי ה’אינדקס’ ל’1’, ואז ‘2’, וכן הלאה. עם זאת, זה לא יעבוד (תוכל לנסות את זה!):

import { sculptureList } from './data.js';

export default function Gallery() {
  let index = 0;

  function handleClick() {
    index = index + 1;
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

המטפל באירוע handleClick מעדכן משתנה מקומי, index. אבל שני דברים מונעים מהשינוי הזה להיות גלוי:

  1. משתנים מקומיים לא נמשכים בין רינדור. כאשר React מעבד את הרכיב הזה פעם שנייה, הוא מעבד אותו מאפס - הוא לא מתחשב בשינויים במשתנים המקומיים.
  2. שינויים במשתנים מקומיים לא יפעילו עיבודים. React לא מבין שהיא צריכה לעבד את הרכיב שוב עם הנתונים החדשים.

כדי לעדכן רכיב בנתונים חדשים, צריכים לקרות שני דברים:

  1. שמור את הנתונים בין העיבודים.
  2. טריגר הגיבו לעיבוד הרכיב עם נתונים חדשים (עיבוד מחדש).

ה- useState Hook מספק את שני הדברים האלה:

  1. משתנה מצב לשמירה על הנתונים בין העיבודים.
  2. פונקציה קובעת מצב לעדכון המשתנה ולהפעיל את React כדי לעבד שוב את הרכיב.

הוספת משתנה מצב

כדי להוסיף משתנה מצב, ייבא ‘useState’ מ-React בחלק העליון של הקובץ:

import { useState } from 'react';

לאחר מכן, החלף את השורה הזו:

let index = 0;

עִם

const [index, setIndex] = useState(0);

index הוא משתנה מצב ו-setIndex הוא פונקציית הקובע.

התחביר [ ו] כאן נקרא destructuring array והוא מאפשר לקרוא ערכים ממערך. למערך המוחזר על ידי ‘useState’ יש תמיד בדיוק שני פריטים.

כך הם עובדים יחד בhandleClick:

function handleClick() {
setIndex(index + 1);
}

כעת לחיצה על כפתור “הבא” משנה את הפסל הנוכחי:

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);

  function handleClick() {
    setIndex(index + 1);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

פגוש את ה-Hook הראשון שלך

ב-React, useState, כמו גם כל פונקציה אחרת שמתחילה ב-“use`”, נקראת Hook.

Hooks הם פונקציות מיוחדות שזמינות רק בזמן ש-React הוא rendering (עליהן ניכנס ביתר פירוט בעמוד הבא). הם מאפשרים לך “להתחבר” לתכונות שונות של React.

הstate היא רק אחת מהתכונות הללו, אבל תפגוש את הHooks האחרים מאוחר יותר.

Pitfall

Hooks — פונקציות שמתחילות ב-‘use’ — ניתן לקרוא רק ברמה העליונה של הרכיבים שלך או Hooks משלך. אתה לא יכול לקרוא ל-Hooks בתוך תנאים, לולאות או פונקציות מקוננות אחרות. ווים הם פונקציות, אבל כדאי לחשוב עליהם כעל הצהרות ללא תנאי לגבי הצרכים של הרכיב שלך. אתה “משתמש” בתכונות React בחלק העליון של הרכיב שלך בדומה לאופן שבו אתה “מייבא” מודולים בחלק העליון של הקובץ שלך.

Anatomy of useState

כשאתה קורא ל-useState, אתה אומר ל-React שאתה רוצה שהרכיב הזה יזכור משהו:

const [index, setIndex] = useState(0);

במקרה זה, אתה רוצה ש-React תזכור את ‘אינדקס’.

Note

המוסכמה היא לתת שם לזוג הזה כמו const [משהו, setSomething]. אתה יכול לקרוא לזה כל מה שאתה אוהב, אבל מוסכמות מקלות על ההבנה של דברים בין פרויקטים.

הארגומנט היחיד ל-‘useState’ הוא הערך ההתחלתי של משתנה הstate שלך. בדוגמה זו, הערך ההתחלתי של אינדקס מוגדר ל-0 עם useState(0).

בכל פעם שהרכיב שלך מעבד, ‘useState’ נותן לך מערך המכיל שני ערכים:

  1. משתנה הstate (אינדקס) עם הערך ששמרת.
  2. פונקציית מצב קובע (setIndex) שיכולה לעדכן את משתנה הstate ולהפעיל את React לעיבוד הרכיב שוב.

הנה איך זה קורה בפעולה:

const [index, setIndex] = useState(0);
  1. הרכיב שלך מעבד בפעם הראשונה. מכיוון שהעברת ‘0’ ל’useState’ כערך ההתחלתי של ‘index’, הוא יחזיר את ‘[0, setIndex]‘. React זוכר ש’0’ הוא ערך הstate האחרון.
  2. אתה מעדכן את הstate. כאשר משתמש לוחץ על הכפתור, הוא קורא setIndex(index + 1). index הוא 0, אז זה setIndex(1). זה אומר ל-React לזכור ש’אינדקס’ הוא ‘1’ עכשיו ומפעיל רינדור נוסף.
  3. הרינדור השני של הרכיב שלך. React עדיין רואה את useState(0), אך מכיוון ש-React זוכר שהגדרת אינדקס ל-1, הוא מחזיר במקום [1, setIndex].
  4. וכן הלאה!

מתן משתני מצב מרובים לרכיב

אתה יכול לקבל כמה משתני מצב של כמה סוגים שאתה רוצה ברכיב אחד. לרכיב זה יש שני משתני מצב, ‘אינדקס’ מספר ו-‘showMore’ בוליאני שמופעל כאשר אתה לוחץ על “הצג פרטים”:

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
    </>
  );
}

זה רעיון טוב שיהיו משתני מצב מרובים אם הstate שלהם אינו קשור, כמו ‘אינדקס’ ו-‘showMore’ בדוגמה זו. אבל אם אתה מגלה שלעתים קרובות אתה משנה שני משתני מצב ביחד, אולי יהיה קל יותר לשלב אותם לאחד. לדוגמה, אם יש לך טופס עם שדות רבים, נוח יותר לקבל משתנה מצב יחיד שמכיל אובייקט מאשר משתנה מצב לכל שדה. קרא את Choosing the State Structure לקבלת טיפים נוספים.

Deep Dive

איך React יודע לאיזה מצב לחזור?

אולי שמתם לב שהקריאה ‘useState’ לא מקבלת שום מידע לגבי איזה משתנה מצב היא מתייחסת. אין “מזהה” שמועבר לuseState, אז איך הוא יודע איזה ממשתני הstate להחזיר? האם זה מסתמך על קסם כמו ניתוח הפונקציות שלך? התשובה היא לא.

במקום זאת, כדי לאפשר את התחביר התמציתי שלהם, Hooks מסתמכים על סדר קריאה יציב על כל עיבוד של אותו רכיב. זה עובד היטב בפועל מכיוון שאם תפעלו לפי הכלל שלמעלה (“התקשרו רק ל-Hooks ברמה העליונה”), Hooks תמיד ייקרא באותו סדר. בנוסף, פלאגין linter תופס את רוב הטעויות.

באופן פנימי, React מחזיקה מערך של זוגות מצבים עבור כל רכיב. זה גם שומר על אינדקס הזוגות הנוכחי, שמוגדר ל-‘0’ לפני העיבוד. בכל פעם שאתה קורא ‘useState’, React נותן לך את צמד הstates הבא ומגדיל את האינדקס. תוכל לקרוא עוד על המנגנון הזה ב-React Hooks: Not Magic, Just Arrays.

הדוגמה הזו לא משתמשת ב-React אבל היא נותנת לך מושג איך ‘useState’ עובד באופן פנימי:

let componentHooks = [];
let currentHookIndex = 0;

// How useState works inside React (simplified).
function useState(initialState) {
  let pair = componentHooks[currentHookIndex];
  if (pair) {
    // This is not the first render,
    // so the state pair already exists.
    // Return it and prepare for next Hook call.
    currentHookIndex++;
    return pair;
  }

  // This is the first time we're rendering,
  // so create a state pair and store it.
  pair = [initialState, setState];

  function setState(nextState) {
    // When the user requests a state change,
    // put the new value into the pair.
    pair[0] = nextState;
    updateDOM();
  }

  // Store the pair for future renders
  // and prepare for the next Hook call.
  componentHooks[currentHookIndex] = pair;
  currentHookIndex++;
  return pair;
}

function Gallery() {
  // Each useState() call will get the next pair.
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  // This example doesn't use React, so
  // return an output object instead of JSX.
  return {
    onNextClick: handleNextClick,
    onMoreClick: handleMoreClick,
    header: `${sculpture.name} by ${sculpture.artist}`,
    counter: `${index + 1} of ${sculptureList.length}`,
    more: `${showMore ? 'Hide' : 'Show'} details`,
    description: showMore ? sculpture.description : null,
    imageSrc: sculpture.url,
    imageAlt: sculpture.alt
  };
}

function updateDOM() {
  // Reset the current Hook index
  // before rendering the component.
  currentHookIndex = 0;
  let output = Gallery();

  // Update the DOM to match the output.
  // This is the part React does for you.
  nextButton.onclick = output.onNextClick;
  header.textContent = output.header;
  moreButton.onclick = output.onMoreClick;
  moreButton.textContent = output.more;
  image.src = output.imageSrc;
  image.alt = output.imageAlt;
  if (output.description !== null) {
    description.textContent = output.description;
    description.style.display = '';
  } else {
    description.style.display = 'none';
  }
}

let nextButton = document.getElementById('nextButton');
let header = document.getElementById('header');
let moreButton = document.getElementById('moreButton');
let description = document.getElementById('description');
let image = document.getElementById('image');
let sculptureList = [{
  name: 'Homenaje a la Neurocirugía',
  artist: 'Marta Colvin Andrade',
  description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.',
  url: 'https://i.imgur.com/Mx7dA2Y.jpg',
  alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.'  
}, {
  name: 'Floralis Genérica',
  artist: 'Eduardo Catalano',
  description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.',
  url: 'https://i.imgur.com/ZF6s192m.jpg',
  alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.'
}, {
  name: 'Eternal Presence',
  artist: 'John Woodrow Wilson',
  description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."',
  url: 'https://i.imgur.com/aTtVpES.jpg',
  alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.'
}, {
  name: 'Moai',
  artist: 'Unknown Artist',
  description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.',
  url: 'https://i.imgur.com/RCwLEoQm.jpg',
  alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.'
}, {
  name: 'Blue Nana',
  artist: 'Niki de Saint Phalle',
  description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.',
  url: 'https://i.imgur.com/Sd1AgUOm.jpg',
  alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.'
}, {
  name: 'Ultimate Form',
  artist: 'Barbara Hepworth',
  description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.',
  url: 'https://i.imgur.com/2heNQDcm.jpg',
  alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.'
}, {
  name: 'Cavaliere',
  artist: 'Lamidi Olonade Fakeye',
  description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.",
  url: 'https://i.imgur.com/wIdGuZwm.png',
  alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.'
}, {
  name: 'Big Bellies',
  artist: 'Alina Szapocznikow',
  description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.",
  url: 'https://i.imgur.com/AlHTAdDm.jpg',
  alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.'
}, {
  name: 'Terracotta Army',
  artist: 'Unknown Artist',
  description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.',
  url: 'https://i.imgur.com/HMFmH6m.jpg',
  alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.'
}, {
  name: 'Lunar Landscape',
  artist: 'Louise Nevelson',
  description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.',
  url: 'https://i.imgur.com/rN7hY6om.jpg',
  alt: 'A black matte sculpture where the individual elements are initially indistinguishable.'
}, {
  name: 'Aureole',
  artist: 'Ranjani Shettar',
  description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."',
  url: 'https://i.imgur.com/okTpbHhm.jpg',
  alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.'
}, {
  name: 'Hippos',
  artist: 'Taipei Zoo',
  description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.',
  url: 'https://i.imgur.com/6o5Vuyu.jpg',
  alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.'
}];

// Make UI match the initial state.
updateDOM();

אתה לא צריך להבין את זה כדי להשתמש ב-React, אבל אתה עשוי למצוא את זה מודל נפשי מועיל.

הstate מבודדת ופרטית

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

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

import Gallery from './Gallery.js';

export default function Page() {
  return (
    <div className="Page">
      <Gallery />
      <Gallery />
    </div>
  );
}

זה מה שעושה מצב שונה ממשתנים רגילים שאתה עשוי להצהיר בראש המודול שלך. הstate לא קשורה לקריאת פונקציה מסוימת או למקום בקוד, אלא היא “מקומית” למקום הספציפי על המסך. הצגת שני רכיבי <Gallery />, כך שstate שלהם מאוחסן בנפרד.

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

מה אם היית רוצה ששתי הגלריות ישמרו על הstates שלהן מסונכרנים? הדרך הנכונה לעשות זאת ב-React היא להסיר מצב מרכיבי ילד ולהוסיף אותו להורה המשותף הקרוב ביותר שלהם. העמודים הבאים יתמקדו בארגון מצב של רכיב בודד, אך נחזור לנושא זה ב-Sharing State Between Components.

Recap

  • השתמש במשתנה מצב כאשר רכיב צריך “לזכור” מידע מסוים בין עיבודים.
  • משתני מצב מוכרזים על ידי קריאה ל-useState Hook.
  • ווים הם פונקציות מיוחדות שמתחילות ב’שימוש’. הם מאפשרים לך “להתחבר” לתכונות של React כמו מצב.
  • הHooks עשויים להזכיר לך יבוא: צריך לקרוא להם ללא תנאי. Calling Hooks, כולל ‘useState’, תקף רק ברמה העליונה של רכיב או Hook אחר.
  • ה-useState Hook מחזיר זוג ערכים: הstate הנוכחי והפונקציה לעדכן אותו.
  • אתה יכול לקבל יותר ממשתנה מצב אחד. באופן פנימי, React מתאים אותם לפי ההזמנה שלהם.
  • הstate היא פרטית לרכיב. אם תעבדו אותו בשני מקומות, כל עותק יקבל מצב משלו.

Challenge 1 of 4:
השלם את הגלריה

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

לאחר תיקון ההתרסקות, הוסף כפתור “קודם” המציג את הפסל הקודם. זה לא אמור להתרסק על הפסל הראשון.

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
    </>
  );
}