ارسال داده به لایه های عمیق با کانتکست context

8-بهمن-1402 / خواندن 34 دقیقه

بررسی کانتکست در ری اکت با ارسال داده از طریق درخت کامپوننت پدر و فرزندان - React Context

معمولاً شما اطلاعات را از طریق props از یک کامپوننت پدر به یک کامپوننت فرزند منتقل می کنید. اما اگر مجبور باشید آنها را از میان تعداد زیادی کامپوننت عبور دهید، یا اگر بسیاری از کامپوننتهای برنامه شما به اطلاعات مشابهی نیاز داشته باشند، می‌تواند پرمخاطب و ناخوشایند شود. Context به کامپوننت پدر اجازه می دهد تا برخی از اطلاعات را در اختیار هر کامپوننت در زیرشاخه درخت خود قرار دهد - مهم نیست چقدر عمیق است - بدون اینکه به طور صریح از طریق props عبور کند.

مشکل با پاس دادن پراپ ها props

ارسال props یک راه عالی برای انتقال صریح داده ها از طریق درخت رابط کاربری به کامپوننتهایی است که از آن استفاده می کنند.

اما هنگامی که شما نیاز دارید چند پراپ prop را از درخت در شاخه های پایینی و عمیق عبور دهید، یا اگر بسیاری از کامپوننتها به همان پراپ prop نیاز داشته باشند، ارسال پراپها prop می‌تواند پیچیده و ناخوشایند شود. نزدیک‌ترین پدربزرپگ مشترک می‌تواند از کامپوننتهایی که به داده‌ها نیاز دارند بسیار دور باشد، و بالا بردن  state به آن بالا می‌تواند منجر به وضعیتی به نام «پراپ دریلینگ prop dilling» شود.

پراپ دریلینگ Prop drilling

آیا اگر راهی برای انتقال داده به کامپوننتهایی که در درخت به آن نیاز دارند بدون ارسال پراپ ها props، وجود داشته باشد، عالی نیست؟ با ویژگی کانتکست ری اکت context چنین چیزی عملی است.

کانتکست context: یک جایگزین به جای ارسال پراپ ها

کانتکست به کامپوننت پدر اجازه میدهد داده را برای تمام سطوح درخت زیر آن فراهم کند. استفاده های زیادی برای کانتکست وجود دارد. در اینجا یک مثال به شما بیشتر کمک میکند. کد زیر به شما در فهم وجود کانتکست کمک میکند.

function Section({ children }) {
  return (
    <section className="section">
      {children}
    </section>
  );
}

function Heading({ level, children }) {
  switch (level) {
    case 1:
      return <h1>{children}</h1>;
    case 2:
      return <h2>{children}</h2>;
    case 3:
      return <h3>{children}</h3>;
    case 4:
      return <h4>{children}</h4>;
    case 5:
      return <h5>{children}</h5>;
    case 6:
      return <h6>{children}</h6>;
    default:
      throw Error('Unknown level: ' + level);
  }
}

export default function Page() {
  return (
    <Section>
      <Heading level={1}>Title</Heading>
      <Heading level={2}>Heading</Heading>
      <Heading level={3}>Sub-heading</Heading>
      <Heading level={4}>Sub-sub-heading</Heading>
      <Heading level={5}>Sub-sub-sub-heading</Heading>
      <Heading level={6}>Sub-sub-sub-sub-heading</Heading>
    </Section>
  );
}

در کد بالا سه کامپوننت داریم. در کد بالا، پراپ level به کامپوننت Heading ارسال می‌شود. این به این دلیل است که پراپ level در signature کامپوننت Heading تعریف شده است: 

function Heading({ level, children }) 

هنگامی که کامپوننت Heading رندر می‌شود، مقدار پراپ level از شیء props استخراج می‌شود و برای تعیین اینکه کدام برچسب HTML heading رندر شود استفاده می‌شود. در این مورد، از آنجایی که مقدار level برابر با 1 است، کامپوننت یک برچسب h1 رندر می‌کند.

در اینجا چگونگی عملکرد کد آورده شده است:

  1. کامپوننت Heading پراپ level={1} را از کامپوننت Page دریافت می‌کند.
  2. پراپ level از ابجکت props استخراج می‌شود و در یک متغیر محلی ذخیره می‌شود.
  3. عبارت switch برای تعیین اینکه کدام برچسب HTML heading رندر شود استفاده می‌شود.
  4. مورد default هر مقدار نامعتبر پراپ level را می‌گیرد و خطایی رابرمیگرداند.
  5. عبارت return تگهای عنوان HTML انتخاب شده را همراه با پراپ children برمی‌گرداند.

نتیجه:

در کد بالا، پراپ level={1} به کامپوننت Heading ارسال می‌شود و مقدار آن برابر با 1 است. این بدان معناست که کامپوننت Heading یک برچسب h1 را رندر می‌کند.

در کد بالا، پراپ level={2} به کامپوننت Heading ارسال می‌شود و مقدار آن برابر با 2 است. این بدان معناست که کامپوننت Heading یک برچسب h2 را رندر می‌کند.

در کد بالا، پراپ level={3} به کامپوننت Heading ارسال می‌شود و مقدار آن برابر با 3 است. این بدان معناست که کامپوننت Heading یک برچسب h3 را رندر می‌کند.

در کد بالا، پراپ level={4} به کامپوننت Heading ارسال می‌شود و مقدار آن برابر با 4 است. این بدان معناست که کامپوننت Heading یک برچسب h4 را رندر می‌کند.

در کد بالا، پراپ level={5} به کامپوننت Heading ارسال می‌شود و مقدار آن برابر با 5 است. این بدان معناست که کامپوننت Heading یک برچسب h5 را رندر می‌کند.

در کد بالا، پراپ level={6} به کامپوننت Heading ارسال می‌شود و مقدار آن برابر با 6 است. این بدان معناست که کامپوننت Heading یک برچسب h6 را رندر می‌کند.

 ---------------------------------------------------

اکنون به کد زیر توجه کنید: فرض کنید شما میخواهید چندین عنوان با Section یکسان داشته باشید که دارای اندازه یکسان باشند:

function Heading({ level, children }) {
  switch (level) {
    case 1:
      return <h1>{children}</h1>;
    case 2:
      return <h2>{children}</h2>;
    case 3:
      return <h3>{children}</h3>;
    case 4:
      return <h4>{children}</h4>;
    case 5:
      return <h5>{children}</h5>;
    case 6:
      return <h6>{children}</h6>;
    default:
      throw Error('Unknown level: ' + level);
  }
}

function Section({ children }) {
  return (
    <section className="section">
      {children}
    </section>
  );
}

export default function Page() {
  return (
    <Section>
      <Heading level={1}>Title</Heading>
      <Section>
        <Heading level={2}>Heading</Heading>
        <Heading level={2}>Heading</Heading>
        <Heading level={2}>Heading</Heading>
        <Section>
          <Heading level={3}>Sub-heading</Heading>
          <Heading level={3}>Sub-heading</Heading>
          <Heading level={3}>Sub-heading</Heading>
          <Section>
            <Heading level={4}>Sub-sub-heading</Heading>
            <Heading level={4}>Sub-sub-heading</Heading>
            <Heading level={4}>Sub-sub-heading</Heading>
          </Section>
        </Section>
      </Section>
    </Section>
  );
}

عملکرد کد بالا، مثل کد قبلی است و تنها تفاوت احاطه کردن level ها با مقادیر مختلف درون تگهای Secton است.

مثلا در یکی از تگهای سکشن Section با level={3} داریم:
 

<Section>
  <Heading level={3}>About</Heading>
  <Heading level={3}>Photos</Heading>
  <Heading level={3}>Videos</Heading>
</Section>

در کد بالا شما سه بار level={3} را باید بنویسید.

چه خوب میشد اگر میتوانستید پراپ level را به تگ <Section> در کامپوننت Page ارسال کنید و آن را از تگ <Heading> حذف کنید. با این روش شما مطمئن میشدید که همه عنوان ها در یک section یکسان دارای اندازه یکسان هستند. مثل چیزی که در کد زیر آمده است:

<Section level={3}>
  <Heading>About</Heading>
  <Heading>Photos</Heading>
  <Heading>Videos</Heading>
</Section>

اما چگونه کامپوننت <Heading> میتواند level نزدیکترین section به خودش را بفهمد؟

بسیار خوب، شما این کار را با پراپ ها prps به تنهایی نمیتوانید انجام دهید. اینجاست که کانتکست context وارد بازی میشود. شما میتوانید آن را در سه گام انجام دهید:

1- یک کانتکست ایجاد کنید. شما میتوانید آن را LevelContext بنامید، زیرا به level عنوان مربوط میشود.

2- سپس از آن کانتکست در کامپوننتی که به آن داده نیاز دارد استفاده کنید. کامپوننت Heading از LevelContext استفاده خواهد کرد.

3-آن کانتکست را برای کامپوننتی که داده را مشخص میکند ارائه کنید.

کانتکست به یک کامپوننت پدر - حتی در فاصله ای بسیار دور - اجازه میدهد داده ها را در سراسر عمق آن درخت آماده استفاده کند.

 

استفاده از کانتکست در فرزندان با فاصله کم یا زیاد

پس با توجه به آنچه در بالاتر گفته شد سه گام برای کانتکست در ری اکت داریم:

Create

Use

Provide

سه گام کانتکست در ری اکت: یعنی ایجاد، استفاده و فراهم کردن داده

اکنون به شرح گام های بالا میپردازیم:

گام اول: ایجاد یک کانتکست Create the context:

ابتدا میبایستی شما کانتکست را ایجاد کنید. این یعنی برای اینکه کامپوننتهای شما بتوانند از آن استفاده کنند، باید آن را از یک فایل اکسپورت کنید:

import { createContext } from 'react';

export const LevelContext = createContext(1);

فکر نمیکنید کد بالا خیلی ساده باشد؟ تنها آرگومانِ createContext، با یک مقدار پیشفرض یا default value است. در اینجا عدد 1 به بزرگترین سطح عنوان اشاره دارد. اما شما میتوانید هر نوع مقداری حتی یک ابجکت را به آن پاس دهید (بفرستید). در مرحله بعد خواهید دید که اهمیت مقدار پیشفرض در چیست.

گام دوم: استفاده از کانتکست Use the context:

اکنون هوک یوزکانتکست useContext را از ری اکت، و LevelContext را از فایل ایجاده شده ایمپورت import کنید :

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';

توجه: ایمپورت آنچه در بالا گفته شد در کامپوننت Page صورت میگیرد و از آنجایی که همه کامپوننتها تا اینجا در یک فایل نگهداری میشوند نیازی به نگرانی درباره کجا ایمپورت شدن کدهای بالا ندارید. اما در قسمتهای پایین میتوانید کدهای زنده را ببینید که کجا این فایلها ایمپورت میشوند. زیرا در ادامه کامپوننتها را در فایلهای جداگانه قرار میدهیم تا سازماندهی بهتری داشته باشند. اگر این پاراگراف را درک نکردید، نگران نباشید و به خواندن ادامه دهید، چیز مهمی نیست.

اکنون، کامپوننت Heading سطح یا level را از پراپ ها props میخواند:

function Heading({ level, children }) {
  // ...
}

در اینجا پراپ level را حذف میکنیم و مقدار از کانتکستی که به تازگی ایمپورت کردیم یعنی LevelContext خوانده میشود:

export default function Heading({ children }) {
  const level = useContext(LevelContext);
  // ...
}

useContext یک هوک است. درست مانند useState و useReducer، تنها می‌توانید یک هوک را بلافاصله در داخل یک کامپوننت ری اکت فراخوانی کنید (یعنی نمیتوانید هوک را در داخل حلقه‌ها یا شرط ها استفاده کنید). هوک useContext به ری اکت می‌گوید که کامپوننت Heading قصد دارد LevelContext را بخواند.

اکنون که کامپوننت Heading دارای پراپ level نیست، دیگر نیازی به ارسال پراپ level به Heading در فایل JSX مانند این نیست:

 <Section>
  <Heading level={4}>Sub-sub-heading</Heading>
  <Heading level={4}>Sub-sub-heading</Heading>
  <Heading level={4}>Sub-sub-heading</Heading>
</Section>

در عوض، اکنون می‌توانید کامپوننت Heading را بدون پراپ level فراخوانی کنید و React به طور خودکار مقدار level را از Context برای آن فراهم می‌کند.

این بدان معناست که شما دیگر نیازی به نگرانی در مورد ارسال دستی پراپ level به کامپوننت Heading ندارید.

-------------------------------------------------

;کامپوننت Page را به‌روزرسانی کنید تا Section پراپ را به جای Heading دریافت کند:

import Heading from './Heading.js';
import Section from './Section.js';

export default function Page() {
  return (
    <Section level={1}>
      <Heading>Title</Heading>
      <Section level={2}>
        <Heading>Heading</Heading>
        <Heading>Heading</Heading>
        <Heading>Heading</Heading>
        <Section level={3}>
          <Heading>Sub-heading</Heading>
          <Heading>Sub-heading</Heading>
          <Heading>Sub-heading</Heading>
          <Section level={4}>
            <Heading>Sub-sub-heading</Heading>
            <Heading>Sub-sub-heading</Heading>
            <Heading>Sub-sub-heading</Heading>
          </Section>
        </Section>
      </Section>
    </Section>
  );
}

در کد بالا تنها کدهای مربوط به کامپوننت Page را مشاهده میکنید. در زیر میخواهیم کدهای کامپوننت Heading و  Section و LevelContext را مشاهده کنیم.

کاموننت Section در فایل Section.js:

export default function Section({ children }) {
  return (
    <section className="section">
      {children}
    </section>
  );
}

کامپوننت Heading در فایل Heading.js:

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';

export default function Heading({ children }) {
  const level = useContext(LevelContext);
  switch (level) {
    case 1:
      return <h1>{children}</h1>;
    case 2:
      return <h2>{children}</h2>;
    case 3:
      return <h3>{children}</h3>;
    case 4:
      return <h4>{children}</h4>;
    case 5:
      return <h5>{children}</h5>;
    case 6:
      return <h6>{children}</h6>;
    default:
      throw Error('Unknown level: ' + level);
  }
}

فایل کانتکست در LevelContext.js:

import { createContext } from 'react';

export const LevelContext = createContext(1);

------------------------------------------------------------

هشدار: این مثال هنوز کاملاً کار نمی کند! همه سربرگ ها دارای اندازه یکسان هستند زیرا اگرچه از Context استفاده می کنید، اما هنوز آن را provide نکردید یا ارائه نداده اید. ری اکت نمی داند کجا آن را پیدا کند! میتوانید حدس بزنید کانتکست برای کدام کامپوننت باید ارائه شود؟

اگر کانتکست را provide نکنیم یا ارائه ندهیم، ری اکت از مقدار default یا پیش فرضی استفاده می کند که در فایل آن قبلا مشخص کرده ایم. در این مثال، ما 1 را به عنوان آرگومان پیشفرض برای createContext مشخص کرده ایم، بنابراین useContext(LevelContext) سطح 1 را برمی گرداند، و همه آن سربرگ ها را روی <h1> تنظیم می کند. بیایید این مشکل را با داشتن هر بخش که کانتکست Context خود را ارائه دهد، حل کنیم.

اگر میخواهید کدهای زنده بالا را ببینید روی این لینک کلیک کنید. یادتان باشد همه عنوان ها اندازه یکسان دارند. تنها در مرحله بعد است که میتوانید ببینید هر سطح اندازه خودش را دارد. چرا؟ زیرا هنوز یک گام را انجام ندادیم.

 

گام سوم: ارائه کانتکست یا provide کردن کانتکست: Provide the context:

همانطور که در زیر میبینید کامپوننت Section اکنون فرزندان خودش children را رندر میکند:

export default function Section({ children }) {
  return (
    <section className="section">
      {children}
    </section>
  );
}

اکنون children را با ارائه دهنده کانتکست به منظور ارائه LevelContext به آنها احاطه میکنیم (دربر میگیریم). یعنی LevelContext.Provider را در دو طرف {children} قرار دهید. اکنون کد بالا را در پایین، به روزرسانی شده ببینید:

import { LevelContext } from './LevelContext.js';

export default function Section({ level, children }) {
  return (
    <section className="section">
      <LevelContext.Provider value={level}>
        {children}
      </LevelContext.Provider>
    </section>
  );
}

این کد چه کار میکند؟ این به ری اکت می گوید: "اگر هر کامپوننتی در داخل این <Section> از LevelContext درخواست کند، این سطح یا level را به آن بدهید." کامپوننت از  نزدیکترین مقدار <LevelContext.Provider> در بالای درخت رابط کاربری UI استفاده خواهد کرد.

شما میتوانید کدهای زنده را در این لینک ببینید و ویرایش کنید و تغییرات را ببینید.

نتایج مشابه کد اصلی است، اما دیگر نیازی به ارسال پراپ level به هر کامپوننت Heading ندارید! در عوض، کامپوننت Heading سطح عنوان یا heading خود را با پرسیدن از نزدیکترین کامپوننت Section بالای خود تشخیص می دهد.

این کار به این صورت انجام می شود:

  1. شما پراپ level را به <Section> پاس می دهید (میفرستید).
  2. کامپوننت Section فرزندان خود را در <LevelContext.Provider value={level}> دربرمیگیرد یا احاطه میکند:wrap.
  3. کامپوننت Heading با استفاده از useContext(LevelContext) نزدیکترین مقدار LevelContext را در بالای خود دریافت میکند.

استفاده و ارائه context از همان کامپوننت

در حال حاضر، شما هنوز باید سطح هر section را به صورت دستی مشخص کنید. با این حال، می توانید این کار را با استفاده از یک کامپوننت جداگانه برای ارائه و استفاده از کانتکست context انجام دهید. این به شما امکان می دهد تا سطح heading را به صورت مرکزی مدیریت کنید و نیازی به پاس دادن (فرستادن) پراپ level به هر کامپوننت Section ندارید.

از آنجایی که Context به شما اجازه می دهد اطلاعاتی را از یک کامپوننت بالاتر بخوانید، هر کامپوننت Section می تواند سطح را از کامپوننت Section بالاتر خود بخواند و به طور خودکار سطح + 1 را به کامپوننت های پایین تر منتقل کند. در اینجا نحوه انجام آن آورده شده است، کد زیر تغییرات کامپوننت Section است:

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';

export default function Section({ children }) {
  const level = useContext(LevelContext);
  return (
    <section className="section">
      <LevelContext.Provider value={level + 1}>
        {children}
      </LevelContext.Provider>
    </section>
  );
}

با این تغییرات شما نیازی به ارسال پراپ level به <section> یا <Heading> ندارید، اکنون در کامپوننت page میبینید که دیگر هیچ عددی برای level مشخص نشده است:

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>
  );
}

کدهای کامپوننت های دیگر تغییری نمیکنند. فقط کامپوننتهای Section و Page که در بالا ارائه شدند تغییر میکنند. ممکن است سوال شود که شاید من نخواهم عنوان های زیرین را یکی یکی بالا ببرم یعنی از یک تا 6، اما یادتان باشد که این یک مثال ساده است برای اینکه کاربرد کانتکست در ری اکت را درک کنید.

کدهای کامل شده بالا را به صورت کامل در این لینک ببیند.

 

اکنون هم Heading و هم Section سطح را از LevelContext می خوانند تا بفهمند تا چه حد عمیق هستند. و Section فرزندان خود را در LevelContext می پیچد تا مشخص کند که هر چیزی در داخل آن در سطح "عمیق تر" است.

توجه:

این مثال از سطوح heading استفاده می کند زیرا آنها به صورت بصری نشان می دهند که چگونه کامپوننت های تودرتو می توانند Context را override کنند. اما کانتکست برای بسیاری از موارد دیگر نیز مفید است. شما می توانید هر اطلاعاتی را که توسط کل درخت فرعی نیاز است، در درخت به کامپوننتهای پایین بفرستید: تم رنگ فعلی، وضعیت کاربری که در حال حاضر وارد شده است و خیلی چیزهای دیگر.

Context از بین کامپوننت های میانی عبور می کند

می توانید به دلخواه بین کامپوننت ارائه دهنده context و کامپوننتی که از آن استفاده می کند، هر تعداد کامپوننت که دوست دارید قرار دهید. این شامل کامپوننت های ساخته شده مانند <div> و کامپوننت هایی است که ممکن است خودتان بسازید.

در این مثال، همان کامپوننت Post (با حاشیه خط خطی) در دو سطح تودرتو قرار داده شده است. توجه داشته باشید که <Heading> در داخل آن به طور خودکار از نزدیکترین <Section> سطح خود را دریافت می کند، در زیر کدهای کامپوننت ProfilePage را میبینید:
 

import Heading from './Heading.js';
import Section from './Section.js';

export default function ProfilePage() {
  return (
    <Section>
      <Heading>My Profile</Heading>
      <Post
        title="Hello traveller!"
        body="Read about my adventures."
      />
      <AllPosts />
    </Section>
  );
}

function AllPosts() {
  return (
    <Section>
      <Heading>Posts</Heading>
      <RecentPosts />
    </Section>
  );
}

function RecentPosts() {
  return (
    <Section>
      <Heading>Recent Posts</Heading>
      <Post
        title="Flavors of Lisbon"
        body="...those pastéis de nata!"
      />
      <Post
        title="Buenos Aires in the rhythm of tango"
        body="I loved it!"
      />
    </Section>
  );
}

function Post({ title, body }) {
  return (
    <Section isFancy={true}>
      <Heading>
        {title}
      </Heading>
      <p><i>{body}</i></p>
    </Section>
  );
}

کدهای بالا را به صورت زنده و قابل ویرایش در این لینک ببینید.

نتیجه کد بالا را در تصویر زیر ببینید.

برای درک اینکه چه اتفاقی دربالا افتاده است این توضیحات را بخوانید و سپس برای شما تصویر فایل کامپوننت Section را میگذارم:

همانطور که در تصویر بالا میبینید، دو نوع کادر وجود دارد: کادری با رنگ طوسی که هیچ فاصله ای ندارد و یک کادر صورتی رنگ که خط خطی یا فاصله دار است.

در کامپوننت سکشن در داخل تابع و پیش از return از یوزکانتکست و LevelContext برای این هوک استفاده شده است تا این کامپوننت به level دسترسی داشته باشد.

پس از return در داخل تگ section برای اختصاص کلاس CSS شرطی گذاشته شده است: که کلاس section را اعمال کن اما اگر شرط بولین isFancy برقرار بود، کلاس fancy را نیز اعمال کن. در واقع با این کار میخواهد پستهای وبلاگ را از بخش های وبلاگ جدا کند. مثلا پروفایل من دارای حاشیه طوسی است. اما هر آنچه از کامپوننت Post به وجود می آید دارای حاشیه یا کادر صورتی خواهد بود که شامل موارد مشخص شده در تصویر بالا است.

به تصویر فایل Section.js نگاه کنید:
 

در تصویر زیر میتوانید ببینید که کامپوننت Post دارای چه مقداری برای isFancy است:

 

انچه در بالا آمد، بیان این مطلب است که کانتکست از میان کامپوننتهای مختلف عبور میکند و کار خودش را انجام میدهد. لطفا گیج نشوید. تنها یک مقایسه ای از عملکرد ارث بری در CSS و کار کردن کانتکست در میان کامپوننتهای مختلف به تصویر کشده شده است.

شما هیچ کار خاصی برای اینکه این کار انجام شود انجام ندادید. یک بخش (Section) محدوده موردنظر برای درخت درون آن را مشخص می‌کند، بنابراین می‌توانید هر کجا که خواستید یک کامپوننت <Heading> وارد کنید، و اندازه صحیح را خواهد داشت. این را در محیط آزمایشی بالا امتحان کنید!

کانتکست به شما این امکان را می‌دهد که کامپوننت هایی را بنویسید که "با اطراف خود سازگار باشند" و در ادامه خود را به شکل‌های مختلفی نمایش دهند، بسته به اینکه در کجا (یا به عبارت دیگر، در کدام کانتکست) رندر می‌شوند.

چگونگی عملکرد کانتکست ممکن است شما را به یاد ارث‌بری خصوصیات در CSS بیاندازد. در CSS، شما می‌توانید برای یک تگ <div>  خصوصیت color: blue را مشخص کنید و هر گره DOM داخل آن، به هر عمقی که باشد، این رنگ را به ارث می‌برد مگر اینکه یک گره دیگر در وسط آن با خصوصیت color: green آن را بازنویسی کند. به همین ترتیب، در ری اکت، تنها راه برای بازنویسی کانتکستهایی که از بالا می آیند، آن است که فرزندان را با یک provider یا ارائه‌دهنده کانتکست با مقدار متفاوت احاطه کنید (wrap).

در CSS، ویژگی‌های مختلف مانند color و background-color یکدیگر را بازنویسی نمی‌کنند. شما می‌توانید رنگ <div> را به قرمز تنظیم کنید بدون اینکه بر پس‌زمینه‌اش تأثیر بگذارید. به همین ترتیب، مفهوم‌های مختلف ری اکت یکدیگر را بازنویسی نمی‌کنند. هر کانتکستی که با createContext ایجاد کنید کاملاً جدا از دیگران است و کامپوننتهایی را که از آن استفاده میکنند یا آن را ارائه می‌دهند به یکدیگر متصل می‌کند. یک کامپوننت ممکن است از چند کانتکست مختلف استفاده کند یا آن را ارائه دهد آن هم بدون مشکل.

قبل از استفاده از کانتکست

کانتکست برای استفاده بسیار جذاب است! اما این همچنین به این معناست که استفاده از آن بسیار آسان است، و این ممکن است به زیاده روی تبدیل شود. اینکه شما نیاز دارید تا برخی از پراپ ها را چندین سطح پایین‌تر از یکدیگر منتقل کنید، به هیچ وجه به این معنا نیست که باید آن اطلاعات را در کانتکست قرار دهید.

پیشنهاد می‌شود که قبل از استفاده از کانتکست، چندین روش جایگزین را در نظر بگیرید:

1. ابتدا با ارسال ویژگی‌ها شروع کنید:
اگر کامپوننتهای شما ساده نیستند، غیرعادی نیست که یک دوجین پراپ prop را از طریق یک دوجین کامپوننت منتقل کنید. اگرچه ممکن است احساس دلسردی داشته باشد، اما باعث می شود بسیار واضح باشد که کدام کامپوننت  از کدام داده استفاده می کند! شخصی که کد شما را نگهداری می کند خوشحال خواهد شد که جریان داده را با prop ها تصریح کرده اید.

این بدان معناست که در ابتدا، بهتر است از پراپ ها برای انتقال داده بین کامپوننتهای خود استفاده کنید. ممکن است انتقال پراپ ها کمی وقت گیر باشد، اما در نهایت باعث می شود که کد شما واضح تر و قابل نگهداری تر باشد.

2. کامپوننت ها را جدا کنید و JSX را به عنوان فرزندان به آن‌ها منتقل کنید:
  اگر برخی از داده‌ها را از طریق چندین لایه از اجزاء واسطه ارسال می‌کنید که از این داده‌ها استفاده نمی‌کنند (و تنها آن را به سمت پایین منتقل می‌کنند)، این اغلب به این معناست که یادتان رفته است که برخی از کامپوننتها را در مسیر جدا کنید. به عنوان مثال، شاید شما داده‌ها را از طریق ویژگی‌های مانند `posts` به کامپوننتها بصری انتقال می‌دهید که مستقیماً از آن‌ها استفاده نمی‌کنند، مانند `<Layout posts={posts} />`. به جای اینکه Layout از آن به عنوان پراپ استفاده کند، Layout را به عنوان یک پراپ به‌نام `children` تعریف کنید و `<Layout><Posts posts={posts} /></Layout>` را رندر کنید. این باعث کاهش لایه‌های میانی بین کامپوننتها می‌شود که داده‌ها و کامپوننتهایی که به آن نیاز دارند را مشخص می‌کند.

توضیحات بیشتر برای شماره دو:
جداسازی کامپوننتها شامل شکستن کامپوننتهای بزرگ به کامپوننتهای کوچکتر و قابل مدیریت تر است. این به بهبود خوانایی، نگهداری و قابلیت استفاده از کد کمک می کند.

انتقال JSX به عنوان children به شما امکان می دهد JSX را محصور کنید و آن را به عنوان یک پراپ به کامپوننت دیگری منتقل کنید. این نیاز به انتقال پراپ های داده از طریق چندین لایه کامپوننت را از بین می برد، که می تواند کد شما را پیچیده تر و دشوارتر برای درک کند.

مثال:

فرض کنید سناریویی دارید که در آن یک کامپوننت Layout پراپ های داده ای مانند posts را دریافت می کند اما از آنها مستقیماً استفاده نمی کند. در عوض، آنها را به کامپوننت Posts منتقل می کند. این می تواند کد شما را به طور غیر ضروری پیچیده کند.

رویکرد بهتر این است که کامپوننت Posts را جداسازی کنید و آن را به عنوان یک فرزند به کامپوننت Layout منتقل کنید. به این ترتیب، داده ها مستقیماً از کامپوننت Layout به کامپوننت Posts منتقل می شوند، تعداد لایه ها کاهش می یابد و کد ساده تر می شود.

در مجموع، جداسازی کامپوننتها و انتقال JSX به عنوان children می تواند ساختار کد را بهبود ببخشد، پیچیدگی را کاهش دهد و برنامه ری اکت شما را قابل نگهداری تر کند و قابلیت استفاده مجدد آن را بالا ببرد.

در اینجا چند نکته برای حداسازی کامپوننت و انتقال JSX به عنوان children آورده شده است:

به دنبال کامپوننت های بزرگ باشید که می توانند به کامپوننت های کوچکتر تقسیم شوند.
به کامپوننت هایی فکر کنید که می توانند به طور مستقل استفاده شوند.
به کامپوننت هایی فکر کنید که می توانند توسط سایر کامپوننت ها استفاده شوند.

اگر هیچ‌کدام از این راهکارها برای شما مناسب نیستند، آنگاه به کانتکست فکر کنید.

موارد استفاده کانتکست context در ری اکت

طراحی و تم برنامه: 

اگر برنامه شما به کاربر اجازه می دهد ظاهر آن را تغییر دهد (به عنوان مثال انتخال حالت تیره)، می توانید یک provider context را در بالای برنامه خود قرار دهید و از آن context در کامپوننتهایی که نیاز به تنظیم ظاهر بصری خود دارند استفاده کنید.

حساب کاربری کنونی: 

بسیاری از کامپوننتها ممکن است نیاز داشته باشند که وضعیت کاربر کنونی و مشخصاتش را بدانند. قرار دادن آن در context باعث می شود که در هر کجای درخت بتوان به آن دسترسی داشت. برخی از برنامه ها همچنین به شما اجازه می دهند همزمان با چندین حساب کار کنید (به عنوان مثال برای ارسال نظر به عنوان کاربر دیگری). در این موارد، می تواند راحت باشد که بخشی از UI را در یک provider تودرتو با یک مقدار حساب فعلی متفاوت قرار دهید.

مسیریابی: 

اکثر راه حل های مسیریابی از context داخلی برای نگه داشتن مسیر فعلی استفاده می کنند. این روشی است که چگونه هر پیوند می داند که فعال است یا خیر. اگر روتر خود را بسازید، ممکن است بخواهید همین کار را انجام دهید.

مدیریت state: 

همانطور که برنامه شما رشد می کند، ممکن است در نزدیکی بالای برنامه خود مقدار زیادی state داشته باشید. بسیاری از کامپوننتها دورتر در پایین درخت ممکن است بخواهند آن را تغییر دهند. معمولاً از یک reducer همراه با context برای مدیریت state پیچیده و ارسال آن به کامپوننتهای دور بدون دردسر زیاد استفاده می شود.

کانتکست Context محدود به مقادیر استاتیک (ثابت و غیرقابل تغییر) نیست. اگر در رندر بعدی مقدار متفاوتی پاس دهید، ری اکت تمام کامپوننتهایی که در زیر آن را می خوانند به روز می کند! به همین دلیل است که context اغلب در ترکیب با state استفاده می شود.

به طور کلی، اگر برخی از اطلاعات توسط کامپوننتهای دور در قسمت های مختلف درخت مورد نیاز است، این یک نشانه خوب است که context به شما کمک خواهد کرد.

خلاصه

  • کانتکست Context به یک کامپوننت اجازه می دهد تا اطلاعاتی را به کل زیرشاخه های درخت ارائه دهد.
  • برای ارسال context:
  1. آن را با export const MyContext = createContext(defaultValue) ایجاد و صادر کنید.
  2. آن را به هوک useContext(MyContext) پاس دهید تا در هر کامپوننت فرزند، مهم نیست که چقدر عمیق باشد، آن را بخوانید.
  3. فرزندان را در <MyContext.Provider value> بپیچید تا آن را از والد فراهم کنید.
<MyContext.Provider value={...}>
  • Context از هر کامپوننت میانی عبور می کند.
  • Context به شما امکان می دهد کامپوننتهایی بنویسید که خود را با اطراف خود تطبیق دهند.
  • قبل از استفاده از context، سعی کنید پراپ ها یا JSX را به عنوان children پاس دهید.
usecontext آموزش ری اکت کانتکست ری اکت