اندیشیدن به‌شکل کامپوننت

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

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

بعد آن را به‌شکل کامپوننت بازسازی کردم. ناگهان، آن آشوب ساختار پیدا کرد.

کامپوننت واقعاً چیست؟

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

function Button({ children, onClick, variant = 'primary' }) {
  return (
    <button className={`btn btn-${variant}`} onClick={onClick}>
      {children}
    </button>
  );
}

این دکمه اهمیت نمی‌دهد کجا زندگی می‌کند. در یک مُدال، یک فرم یا یک نوار ناوبری کار می‌کند. این قدرتِ کپسوله‌سازی است.

تغییرِ ذهنیت

اندیشیدن به‌شکل کامپوننت به طرزِ فکر متفاوتی نیاز دارد:

  • رابط را بشکن. هر رابط، درختی از تکه‌های کوچک‌تر است.
  • الگوها را بشناس. اگر داری چیزی را دو بار می‌سازی، احتمالاً باید یک کامپوننت باشد.
  • مرزها را تعریف کن. هر کامپوننت باید یک مسئولیت واحد داشته باشد.
  • برای استفاده‌ی دوباره طراحی کن. کامپوننت‌هایی بساز که در بافت‌های مختلف کار کنند.

ترکیب به‌جای پیکربندی

بهترین کامپوننت‌ها قابل‌ترکیب‌اند. به‌جای چپاندنِ هر گزینه در props، بگذار کامپوننت‌ها با هم کار کنند:

// Instead of one massive Card component with 20 props...
<Card
  title="Hello"
  subtitle="World"
  image="/photo.jpg"
  footer="Read more"
  variant="outlined"
/>

// ...compose smaller, focused components
<Card>
  <CardImage src="/photo.jpg" />
  <CardContent>
    <CardTitle>Hello</CardTitle>
    <CardSubtitle>World</CardSubtitle>
  </CardContent>
  <CardFooter>Read more</CardFooter>
</Card>

رویکرد دوم انعطاف‌پذیرتر است. کارتی بدون تصویر می‌خواهی؟ کافی است CardImage را نگذاری. فوترِ سفارشی می‌خواهی؟ CardFooter را با هر چیزی که بخواهی جایگزین کن.

وضعیت آن‌جا زندگی می‌کند که لازم است

یکی از سخت‌ترین بخش‌های اندیشیدنِ کامپوننتی، تصمیم درباره‌ی جایگاهِ وضعیت است. یک قاعده‌ی ساده:

  • وضعیت محلی: فقط همین کامپوننت به آن نیاز دارد (مثلاً بازبودن یک منوی کشویی)
  • وضعیت بالابرده‌شده: چند هم‌نیا به آن نیاز دارند (به والد منتقلش کن)
  • وضعیت سراسری: کامپوننت‌های نامرتبط زیادی به آن نیاز دارند (از context یا یک store استفاده کن)
// Local state — only the dropdown cares
function Dropdown() {
  const [isOpen, setIsOpen] = useState(false);
  // ...
}

// Lifted state — form needs to know all input values
function Form() {
  const [values, setValues] = useState({});
  return (
    <>
      <Input value={values.name} onChange={...} />
      <Input value={values.email} onChange={...} />
    </>
  );
}

کامپوننت‌ها به‌مثابه‌ی مستندات

کامپوننت‌های خوش‌طراحی خودمستندند. وقتی درختِ کامپوننت‌ها را می‌خوانی، باید ساختارِ رابط را بفهمی:

<Page>
  <Header>
    <Logo />
    <Navigation />
    <UserMenu />
  </Header>
  <Main>
    <Sidebar />
    <Content />
  </Main>
  <Footer />
</Page>

نیازی به توضیح نیست. ساختار خودش داستان را می‌گوید.

مزیتِ اشکال‌زدایی

وقتی چیزی در یک سیستم کامپوننت‌محور می‌شکند، دقیقاً می‌دانی کجا را نگاه کنی. باگ یکی از این‌هاست:

  • درونِ کامپوننت (منطقش را بررسی کن)
  • در props‌ای که پاس داده می‌شود (والد را بررسی کن)
  • در مدیریت وضعیت (جایی که وضعیت زندگی می‌کند را بررسی کن)

این را با اشکال‌زداییِ یک فایلِ ۲۰۰۰ خطی که همه‌چیز در آن به هم گره خورده مقایسه کن. کامپوننت‌ها به تو جداسازی می‌دهند، و جداسازی به تو شفافیت می‌دهد.

کوچک شروع کن

لازم نیست کلِ کدبیس را یک‌شبه بازنویسی کنی. با یک کامپوننت شروع کن. جدایش کن، props شفاف بهش بده و ببین چه حسی دارد.

بعد یکی دیگر. و یکی دیگر.

طولی نمی‌کشد که دیگر صفحه نمی‌بینی — سیستم‌هایی از تکه‌های قابل‌استفاده‌ی دوباره و قابل‌ترکیب می‌بینی. و آن‌جاست که ساختنِ رابط واقعاً لذت‌بخش می‌شود.

مقالات مرتبط