ممکن است تا کنون برای درک این که چگونه داده‌ها از طریق برنامه React ما جریان پیدا می‌کنند، دچار مشکل شده باشیم. Prop drilling می‌تواند یکی از دلایل اصلی این اتفاق در برنامه‌های React باشد.

Prop drilling به فرآیند عبور دادن propها از لایه‌های متعدد کامپوننت‌ها اشاره دارد، حتی زمانی که برخی از آن کامپوننت‌ها به طور مستقیم از propها استفاده نمی‌کنند.

این موضوع می‌تواند منجر به ایجاد چالش‌هایی مانند مشکلات دیباگ کردن، رفتار غیرمنتظره prop mutationها و کامپوننت‌هایی شود که با هم مرتبط هستند و استفاده مجدد از آن‌ها دشوار است.

در این مقاله قصد داریم تا درمورد این که Prop drilling چیست و چه مشکلاتی دارد صحبت کنیم. همینطور با تکنیک‌هایی آشنا می‌شویم که به کمک آن‍ها می‌توانیم کامپوننت‌های خود را مستقل نگه داریم و قابلیت نگه‌داری کد خود را افزایش دهیم.

Prop Drilling چیست؟

Prop drilling، همچنین به عنوان threading props یا component chaining شناخته می‌شود. این مفهوم به فرآیند انتقال داده‌ها از یک کامپوننت اصلی به کامپوننت‌های child تودرتو از طریق props اشاره دارد.

موضوع Prop drilling زمانی اتفاق می‌افتد که یک prop باید از چندین لایه کامپوننت تودرتو عبور داده شود تا به یک کامپوننت child عمیق تودرتو که به آن prop نیاز دارد برسد. هر کامپوننت میانی در سلسله مراتب باید prop را به پایین منتقل نماید، حتی اگر خود از آن استفاده نکند.

سناریویی را در نظر می‌گیریم که در آن یک کامپوننت سطح بالا داریم که داده‌ها را از یک API دریافت می‌کند. پس از دریافت داده‌ها، باید آن‌ها را به کامپوننت‌های child تودرتوی متعدد منتقل نماید.

به جای اینکه داده‌ها را مستقیما به هر کامپوننت child ارسال کنیم، آن‌ها را از هر کامپوننت واسطه در سلسله مراتب عبور می‌دهیم تا به کامپوننت child مورد نظر برسد. این عبور propها از سطوح مختلف کامپوننت‌ها همان چیزی است که Prop drilling مستلزم آن می‌باشد. به عنوان مثال:

// ParentComponent.js
import React from 'react';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const data = 'Hello from Parent';

  return (
    <div>
      <ChildComponent data={data} />
    </div>
  );
}

export default ParentComponent;
// ChildComponent.js
import React from 'react';
import GrandchildComponent from './GrandchildComponent';

function ChildComponent(props) {
  return (
    <div>
      <GrandchildComponent data={props.data} />
    </div>
  );
}

export default ChildComponent;
// GrandchildComponent.js
import React from 'react';

function GrandchildComponent(props) {
  return <div>{props.data}</div>;
}

export default GrandchildComponent;

در این مثال، GrandchildComponent نیاز به دسترسی به prop data دارد، اما ParentComponent و ChildComponent از آن استفاده نمی‌کنند. با این حال، prop data همچنان باید از طریق آن‌ها منتقل شود.

چالش‌های Prop Drilling

کد پیچیده و Boilerplate

Prop drilling می‌تواند منجر به افزایش پیچیدگی و کد boilerplate شود، به خصوص زمانی که درخت کامپوننت بزرگ‌تری داشته باشیم. با عمیق‌تر شدن کامپوننت‌ها، مدیریت جریان propها چالش‌برانگیزتر می‌شود و می‌تواند پایگاه کد را به هم بریزد.

// Example of Prop Drilling
const ParentComponent = () => {
    const data = fetchData(); // Assume fetching data from an API
    return (
        <ChildComponentA data={data} />
    );
};

const ChildComponentA = ({ data }) => {
    return (
        <ChildComponentB data={data} />
    );
};

const ChildComponentB = ({ data }) => {
    return (
        <ChildComponentC data={data} />
    );
};

// This continues...

اتصال کامپوننت‌ها

Prop drilling می‌تواند کامپوننت‌ها را محکم به هم متصل کند و بازسازی یا تغییر ساختار سلسله مراتب کامپوننت‌ها را بدون تأثیر بر سایر بخش‌های برنامه سخت‌تر کند. این موضوع می‌تواند منجر به کاهش قابلیت نگه‌داری و انعطاف‌پذیری کد شود.

سربار عملکرد

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

روش‌های جلوگیری از مشکلات Prop Drilling

چندین تکنیک برای غلبه بر مشکلات Prop drilling در React.js وجود دارد که عبارتند از:

در ادامه مثال قبل را با استفاده از Context API دوباره می‌نویسیم:

// MyContext.js
import React from 'react';

const MyContext = React.createContext();

export default MyContext;
// ParentComponent.js
import React from 'react';
import ChildComponent from './ChildComponent';
import MyContext from './MyContext';

function ParentComponent() {
  const data = 'Hello from Parent';

  return (
    <MyContext.Provider value={data}>
      <ChildComponent />
    </MyContext.Provider>
  );
}

export default ParentComponent;
// ChildComponent.js
import React from 'react';
import GrandchildComponent from './GrandchildComponent';
import MyContext from './MyContext';

function ChildComponent() {
  return (
    <MyContext.Consumer>
      {data => <GrandchildComponent data={data} />}
    </MyContext.Consumer>
  );
}

export default ChildComponent;
// GrandchildComponent.js
import React from 'react';
import MyContext from './MyContext';

function GrandchildComponent() {
  return (
    <MyContext.Consumer>
      {data => <div>{data}</div>}
    </MyContext.Consumer>
  );
}

export default GrandchildComponent;

در این مثال بازنویسی شده، ما از Context API برای تهیه و مصرف prop data  بدون نیاز به انتقال دستی از طریق هر کامپوننت استفاده کرده‌ایم.

جمع‌بندی

در این مقاله سعی کردیم تا با مفهوم Prop drilling در برنامه‌های React آشنا شویم. استفاده از Prop drilling در ابتدا ممکن است روش مناسبی به نظر برسد اما عواقب آن می‌تواند قابلیت نگه‌داری کد ما به شدت کاهش دهد. با استفاده از تکنیک‌هایی مانند Context API، کتابخانه‌های مدیریت state، یا قدرت render props، کاری می‌کنیم تا بتوانیم برنامه‌های React تمیز، با قابلیت نگه‌داری بالا و مقیاس‌پذیر بسازیم.