קונטקסט

קונטקסט מספק דרך להעביר מידע דרך עץ הקומפוננטות בלי להשתמש ב-props באופן ידני לכל קומפוננטה.

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

מתי להשתמש בקונטקסט

הקונטקסט נועד לשתף מידע שנחשב ״גלובאלי״ לכל הקומפוננטות בעץ, כמו מידע על המשתמש המאומת, ערכת הנושא או השפה המועדפת. בקוד הנ״ל אנחנו מעבירים את ה-prop של ״ערכת הנושא״ בשביל לעצב את קומפוננטת הכפתור:

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  // "ערכת נושא" prop-קומפוננטת התפריט צריכה לקבל את ה  // זה יכול להפוך את הקוד ."ThemedButton" ולהעביר אותו לכפתור  // למסורבל אם כל כפתור באפליקציה צריך לדעת על ערכת הנושא בצורה זו  // כי נאלץ להעביר אותה דרך כל הקומפוננטות.  return (
    <div>
      <ThemedButton theme={props.theme} />    </div>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}

בעזרת הקונטקסט, אפשר להמנע מלהעביר את ה-prop דרך רכיבי ביניים:

// קונטקסט נותן לנו להעביר ערך עמוק לתוך עץ הקומפוננטות// בלי להעביר אותו באופן מפורש לכל קומפוננטה.// (צור קונטקסט לערכת הנושא (עם ברירת מחדל ״בהירה״const ThemeContext = React.createContext('light');
class App extends React.Component {
  render() {
    // כדי להעביר את ערכת הנושא לעץ הקומפוננטות Provider-נשתמש ב    // כל קומפוננטה יכולה להשתמש בו, לא משנה באיזה עומק.    // בדוגמא הבאה, נעביר ״כהה״ כערך לערכת הנושא.    return (
      <ThemeContext.Provider value="dark">        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// קומפוננטת ביניים כבר לא צריכה להעביר// את ערכת הנושא.function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // contextType-נקרא את ערך ערכת הנושא מהקונטקסט ב.  // ימצא את ספק ערכת הנושא הקרוב ויקרא את ערך ערכת הנושא React  // בדוגמא הזאת, הערך הוא ״כהה״.  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;  }
}

לפני השימוש בקונטקסט

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

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

לדוגמא, קומפוננטת Page שמעבירה את ה-props user ו- avatarSize לכמה רמות עומק, כדי שקומפוננטות ילד כמו Link ו- Avatar יוכלו להשתמש בהם:

<Page user={user} avatarSize={avatarSize} />
// ... שמרנדרת ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... שמרנדרת ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... שמרנדרת ...
<Link href={user.permalink}>
  <Avatar user={user} size={avatarSize} />
</Link>

יכול להיות שהעברת ה-props user ו- avatarSize דרך כל כך הרבה רמות עומק תרגיש מיותר, בעיקר כי בסוף רק קומפוננטת ה- Avatar באמת משתמשת בהם. זה גם מעצבן שכל פעם שקומפוננטת ה- Avatar צריכה עוד props, צריך להעביר אותם דרך כל רכיבי הביניים.

דרך אחת לפתור את הבעיה ללא שימוש בקונטקסט היא להעביר את קומפוננטת ה-Avatar עצמה כדי שקומפוננטות הביניים לא יצטרכו לדעת על ה-props user או avatarSize:

function Page(props) {
  const user = props.user;
  const userLink = (
    <Link href={user.permalink}>
      <Avatar user={user} size={props.avatarSize} />
    </Link>
  );
  return <PageLayout userLink={userLink} />;
}

// :עכשיו, יש לנו
<Page user={user} avatarSize={avatarSize} />
// ... שמרנדרת ...
<PageLayout userLink={...} />
// ... שמרנדרת ...
<NavigationBar userLink={...} />
// ... שמרנדרת ...
{props.userLink}

עם השינוי הזה, רק הקומפוננטה העליונה Page צריכה לדעת על קומפוננטות ה- Link וה- Avatar ועל ה-props שהן דורשות.

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

אין הגבלה של ילד יחיד לכל קומפוננטה. אפשר להעביר ילדים מרובים, ואפילו ״משבצות״ (“slots”) מרובות לילדים, כמתועד כאן:

function Page(props) {
  const user = props.user;
  const content = <Feed user={user} />;
  const topBar = (
    <NavigationBar>
      <Link href={user.permalink}>
        <Avatar user={user} size={props.avatarSize} />
      </Link>
    </NavigationBar>
  );
  return (
    <PageLayout
      topBar={topBar}
      content={content}
    />
  );
}

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

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

ממשק תכנות

React.createContext

const MyContext = React.createContext(defaultValue);

הקוד הזה יוצר אובייקט קונטקסט. כש- React מרנדר את הקומפוננטות שמאזינות לקונטקסט, הוא קורא את ערך הקונטקסט מהספק (“Provider”) הקרוב ביותר מעליו בעץ.

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

Context.Provider

<MyContext.Provider value={/* some value */}>

כל עצם קונטקסט מגיע עם קומפוננטת ספק (Provider) שנותנת לקומפוננטות שצורכות אותו להקשיב לשינויים בקונטקסט. הספק מקבל prop value שיועבר לקומפוננטות ילד שצורכות את הספק בכל רמות העומק של העץ. ספק אחד יכול להתחבר לצרכנים רבים. אפשר להגדיר ספקים ברמות שונות של אותו העץ כדי לעקוף את הערכים המוגדרים בהם בעומקים שונים של עץ הקומפוננטות.

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

כל הצרכנים שהם צאצאים של ספק, ירונדרו בכל שינוי ב-prop value. ההפצה מהספק לצאצאיו הצרכנים (כולל .contextType ו-useContext) אינה כפופה למתודה shouldComponentUpdate, כך שהצרכן מתעדכן אפילו כשקומפוננטה מעליו מדלגת על העדכון.

שינויים נקבעים ע״י השוואת הערכים החדשים מול הישנים בעזרת אותו האלגוריתם כמו Object.is.

הערה

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

Class.contextType

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* MyContext-בשימוש בערך ה mount-יגרום לתופעת לואי בזמן ה */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /*MyContext-ירנדר משהו שמבוסס על ערך ה */
  }
}
MyClass.contextType = MyContext;

מאפיין ה-contextType במחלקה מוקצה בעצם קונטקסט שנוצר על ידי המתודה React.createContext(). שימוש במאפיין זה נותן לנו לצרוך את ערך הקונטקסט הנוכחי הקרוב ביותר בעזרת this.context. אפשר להשתמש בהפניה זו בכל אחת ממתודות מחזור החיים כולל מתודת הרינדור.

הערה:

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

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

class MyClass extends React.Component {
  static contextType = MyContext;
  render() {
    let value = this.context;
    /* רינדור משהו בהתאם לערך */
  }
}

Context.Consumer

<MyContext.Consumer>
  {value => /* רינדור משהו בהתאם לערך */}
</MyContext.Consumer>

קומפוננטת React שמקשיבה לשינויים בקונטקסט. שימוש בקומפוננטה זו מאפשר לצרוך קונטקסט מתוך קומפוננטת פונקציה.

דורשת פונקציה בתור ילד. הפונקציה הזאת מקבלת את ערך הקונטקסט הנוכחי ומחזירה צומת React. ערך הארגומנט שמועבר לפונקציה יהיה זהה ל- value prop של ספק הקונטקסט הקרוב ביותר מעלינו בעץ. אם אין ספק לקונטקס, הערך יהיה זהה לערך ברירת המחדל שנקבע בזמן יצירת הקונטקסט (עם createContext()).

הערה

למידע נוסף על תבנית ״פונקציה כילד״ בקרו בעמוד render props.

Context.displayName

אובייקט context מקבל מאפיין displayName מסוג מחרוזת. React DevTools משתמש במחרוזת זו על מנת לקבוע מה להציג ל-context.

לדוגמה, הקומפוננטה הבאה תופיע כ-MyDisplayName ב-DevTools:

const MyContext = React.createContext(/* ערך מסויים */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" ב-DevTools
<MyContext.Consumer> // "MyDisplayName.Consumer" ב-DevTools

דוגמאות

קונטקסט דינאמי

דוגמא מורכבת יותר עם ערך דינאמי של ערכת הנושא:

theme-context.js

export const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
};

export const ThemeContext = React.createContext(  themes.dark // ברירת מחדל);

themed-button.js

import {ThemeContext} from './theme-context';

class ThemedButton extends React.Component {
  render() {
    let props = this.props;
    let theme = this.context;    return (
      <button
        {...props}
        style={{backgroundColor: theme.background}}
      />
    );
  }
}
ThemedButton.contextType = ThemeContext;
export default ThemedButton;

app.js

import {ThemeContext, themes} from './theme-context';
import ThemedButton from './themed-button';

// ThemedButton-רכיב ביניים שמשתמש ב
function Toolbar(props) {
  return (
    <ThemedButton onClick={props.changeTheme}>
      Change Theme
    </ThemedButton>
  );
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: themes.light,
    };

    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };
  }

  render() {
    // בתוך ספק ערכת הנושא ThemedButton-כפתור ה    // והכפתור החיצוני ,state-משתמש בערכת הנושא מה    // משתמש בברירת המחדל של ערכת הנושא הכהה    return (
      <Page>
        <ThemeContext.Provider value={this.state.theme}>          <Toolbar changeTheme={this.toggleTheme} />        </ThemeContext.Provider>        <Section>
          <ThemedButton />        </Section>
      </Page>
    );
  }
}

const root = ReactDOM.createRoot(
  document.getElementById('root')
);
root.render(<App />);

עדכון הקונטקסט מתוך קומפוננטה מקוננת

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

theme-context.js

// createContext-שים לב שצורת הערך של ברירת המחדל שמועבר ל
// זהה לצורת הערכים שהצרכנים מצפים לקבל!
export const ThemeContext = React.createContext({
  theme: themes.dark,  toggleTheme: () => {},});

theme-toggler-button.js

import {ThemeContext} from './theme-context';

function ThemeTogglerButton() {
  // מקבל לא רק את ערכת הנושא Theme Toggler-כפתור ה  // מהקונטקסט כדי לעדכן את ערכה toggleTheme אלא גם את הפונקציה  return (
    <ThemeContext.Consumer>
      {({theme, toggleTheme}) => (        <button
          onClick={toggleTheme}
          style={{backgroundColor: theme.background}}>
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

export default ThemeTogglerButton;

app.js

import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };

    // מכיל גם את פונקצית העדכון state-ה    // שתועבר לספק הקונטקסט    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,    };
  }

  render() {
    // בשלמותו מועבר לספק state-ה    return (
      <ThemeContext.Provider value={this.state}>        <Content />
      </ThemeContext.Provider>
    );
  }
}

function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}

const root = ReactDOM.createRoot(
  document.getElementById('root')
);
root.render(<App />);

שימוש ביותר מקונטקסט אחד

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

// קונטקסט ערכת הנושא עם ברירת מחדל בהירה
const ThemeContext = React.createContext('light');

// קונטקסט המשתמש המאומת
const UserContext = React.createContext({
  name: 'Guest',
});

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // קומפוננטה שמספקת ערכי ברירת מחדל לקונטקסט
    return (
      <ThemeContext.Provider value={theme}>        <UserContext.Provider value={signedInUser}>          <Layout />
        </UserContext.Provider>      </ThemeContext.Provider>    );
  }
}

function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  );
}

// קומפוננטה יכולה לצרוך יותר מקונטקסט אחד
function Content() {
  return (
    <ThemeContext.Consumer>      {theme => (        <UserContext.Consumer>          {user => (            <ProfilePage user={user} theme={theme} />          )}        </UserContext.Consumer>      )}    </ThemeContext.Consumer>  );
}

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

הסתיגויות

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

class App extends React.Component {
  render() {
    return (
      <MyContext.Provider value={{something: 'something'}}>        <Toolbar />
      </MyContext.Provider>
    );
  }
}

כדי לעקוף את הבעיה הזאת, אפשר להעביר את הערך ל-state של האב:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'},    };
  }

  render() {
    return (
      <MyContext.Provider value={this.state.value}>        <Toolbar />
      </MyContext.Provider>
    );
  }
}

ממשק תכנות מדור קודם

הערה

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

האם התוכן בעמוד זה היה שימושי?לעריכת העמוד הנוכחי