İçeriğe geç
react

React Children ve Composition Pattern'leri

Tarık Tunç
React Children ve Composition Pattern'leri

React Children ve Composition Pattern'leri

React'in en güçlü özelliklerinden biri composition (bileşim) modelidir. Bileşenleri birbirinin içine yerleştirerek, esnek ve yeniden kullanılabilir UI yapıları oluşturabilirsiniz. Bu yazıda children prop'u, composition pattern'leri ve ileri düzey kullanım senaryolarını detaylıca inceleyeceğiz.

Children Prop Nedir?

children, React bileşenlerinde açılış ve kapanış etiketleri arasına yerleştirilen her şeyi temsil eden özel bir prop'tur. JSX'te bir bileşenin içine yazdığınız herhangi bir içerik, o bileşene props.children olarak aktarılır.

// Basit children kullanımı
function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

// Kullanım
function App() {
  return (
    <Card>
      <h3>Başlık</h3>
      <p>İçerik burada yer alır.</p>
    </Card>
  );
}

Children Türleri

React children birçok farklı türde olabilir:

  • String: Düz metin içeriği
  • JSX Elemanları: Bir veya birden fazla React elemanı
  • Fonksiyon: Render prop pattern'i için fonksiyon
  • Boolean/Null/Undefined: Hiçbir şey render edilmez
  • Dizi: Birden fazla eleman listesi
// Farklı children türleri
<Wrapper>Düz metin</Wrapper>
<Wrapper><span>JSX Elemanı</span></Wrapper>
<Wrapper>{(data) => <span>{data}</span>}</Wrapper>
<Wrapper>{null}</Wrapper>

React.Children API

React, children prop'u üzerinde işlem yapmak için React.Children utility API'si sunar. Bu API, children'ın yapısını güvenli bir şekilde manipüle etmenize olanak tanır.

import { Children, cloneElement } from 'react';

function List({ children }) {
  return (
    <ul>
      {Children.map(children, (child, index) => (
        <li key={index}>{child}</li>
      ))}
    </ul>
  );
}

// Children.count — çocuk sayısını döner
function CountChildren({ children }) {
  const count = Children.count(children);
  return <p>Toplam {count} çocuk eleman var.</p>;
}

// Children.toArray — children'ı düz diziye çevirir
function SortedList({ children }) {
  const sorted = Children.toArray(children).sort((a, b) =>
    a.props.value.localeCompare(b.props.value)
  );
  return <ul>{sorted}</ul>;
}

// Children.only — tek çocuk olmasını zorunlu kılar
function SingleChild({ children }) {
  const child = Children.only(children);
  return cloneElement(child, { className: 'highlighted' });
}

Composition Pattern: Containment

Containment pattern'i, bir bileşenin içeriğini önceden bilmediği durumlarda kullanılır. Özellikle genel amaçlı "kutu" bileşenleri (Card, Dialog, Sidebar) için idealdir.

function Dialog({ title, children, footer }) {
  return (
    <div className="dialog-overlay">
      <div className="dialog">
        <div className="dialog-header">
          <h2>{title}</h2>
        </div>
        <div className="dialog-body">
          {children}
        </div>
        {footer && (
          <div className="dialog-footer">
            {footer}
          </div>
        )}
      </div>
    </div>
  );
}

// Kullanım
<Dialog
  title="Onay"
  footer={<button onClick={handleConfirm}>Tamam</button>}
>
  <p>Bu işlemi onaylıyor musunuz?</p>
</Dialog>

Composition Pattern: Specialization

Specialization, daha genel bir bileşenin özel bir versiyonunu oluşturma yöntemidir. Bir bileşen, başka bir bileşeni belirli prop'larla sarmalayarak özelleştirilir.

function Button({ variant = 'default', size = 'md', children, ...props }) {
  return (
    <button className={`btn btn-${variant} btn-${size}`} {...props}>
      {children}
    </button>
  );
}

// Specialization — özelleştirilmiş butonlar
function PrimaryButton({ children, ...props }) {
  return <Button variant="primary" {...props}>{children}</Button>;
}

function DangerButton({ children, ...props }) {
  return <Button variant="danger" {...props}>{children}</Button>;
}

function IconButton({ icon, children, ...props }) {
  return (
    <Button {...props}>
      <span className="icon">{icon}</span>
      {children}
    </Button>
  );
}

Compound Component Pattern

Compound component pattern, birbiriyle ilişkili bileşenlerin birlikte çalışmasını sağlar. Bu pattern, API'yi daha deklaratif ve esnek hale getirir.

import { createContext, useContext, useState } from 'react';

const TabsContext = createContext();

function Tabs({ children, defaultTab }) {
  const [activeTab, setActiveTab] = useState(defaultTab);

  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

function TabList({ children }) {
  return <div className="tab-list" role="tablist">{children}</div>;
}

function Tab({ value, children }) {
  const { activeTab, setActiveTab } = useContext(TabsContext);
  return (
    <button
      role="tab"
      className={activeTab === value ? 'tab active' : 'tab'}
      onClick={() => setActiveTab(value)}
    >
      {children}
    </button>
  );
}

function TabPanel({ value, children }) {
  const { activeTab } = useContext(TabsContext);
  if (activeTab !== value) return null;
  return <div role="tabpanel">{children}</div>;
}

// Bileşenleri Tabs'a bağla
Tabs.TabList = TabList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;

// Kullanım
<Tabs defaultTab="tab1">
  <Tabs.TabList>
    <Tabs.Tab value="tab1">Genel</Tabs.Tab>
    <Tabs.Tab value="tab2">Ayarlar</Tabs.Tab>
  </Tabs.TabList>
  <Tabs.Panel value="tab1">Genel içerik</Tabs.Panel>
  <Tabs.Panel value="tab2">Ayarlar içeriği</Tabs.Panel>
</Tabs>

Render Props Pattern

Render props, children olarak bir fonksiyon geçirerek bileşenler arasında mantık paylaşımı sağlar. Hook'lar yaygınlaşmadan önce çok popülerdi, ancak belirli senaryolarda hala kullanışlıdır.

function MouseTracker({ children }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };

  return (
    <div onMouseMove={handleMouseMove} style={{ height: '100vh' }}>
      {children(position)}
    </div>
  );
}

// Kullanım
<MouseTracker>
  {({ x, y }) => (
    <p>Mouse pozisyonu: {x}, {y}</p>
  )}
</MouseTracker>

Slot Pattern

Slot pattern, bileşene birden fazla children alanı tanımlamak için named props kullanır. Vue.js'teki slot kavramına benzer şekilde çalışır.

function Layout({ header, sidebar, children, footer }) {
  return (
    <div className="layout">
      <header className="layout-header">{header}</header>
      <div className="layout-body">
        <aside className="layout-sidebar">{sidebar}</aside>
        <main className="layout-content">{children}</main>
      </div>
      <footer className="layout-footer">{footer}</footer>
    </div>
  );
}

// Kullanım
<Layout
  header={<Navigation />}
  sidebar={<MenuList />}
  footer={<Copyright />}
>
  <h1>Ana Sayfa İçeriği</h1>
  <p>Hoş geldiniz!</p>
</Layout>
İpucu: Composition, inheritance yerine tercih edilmelidir. React ekibi de bileşenler arasında kod paylaşımı için kalıtım yerine composition kullanmanızı önerir. Bu sayede bileşenleriniz daha esnek, test edilebilir ve bakımı kolay olur.

cloneElement ile Children Manipülasyonu

cloneElement, mevcut bir React elemanını kopyalayarak ona yeni prop'lar eklemek için kullanılır. Özellikle children'a otomatik prop enjeksiyonu gereken durumlarda faydalıdır.

function FormGroup({ label, error, children }) {
  return (
    <div className="form-group">
      <label>{label}</label>
      {Children.map(children, (child) =>
        cloneElement(child, {
          className: error ? 'input-error' : '',
          'aria-invalid': !!error,
        })
      )}
      {error && <span className="error-text">{error}</span>}
    </div>
  );
}
Uyarı: cloneElement kullanımı React ekibi tarafından artık önerilmiyor. Bunun yerine render props veya custom hook'lar tercih edin. React 19 ile birlikte bu API deprecated olarak işaretlenmiştir.

Sonuç

React Children ve composition pattern'leri, uygulamanızın mimarisini belirleyen temel kavramlardır. Containment ile genel amaçlı sarmalayıcılar, specialization ile özelleştirilmiş bileşenler, compound component ile deklaratif API'ler ve render props ile mantık paylaşımı yapabilirsiniz. Modern React'te hook'lar birçok pattern'in yerini almış olsa da, composition hala UI yapıları oluşturmanın en etkili yoludur. Bileşenlerinizi küçük, tek sorumlu ve composable tutarak sürdürülebilir projeler inşa edebilirsiniz.

Kaynaklar

İlgili Yazılar