asChild

Bileşen içinde özel bir öğeyi render etmek için `asChild` prop'unun nasıl kullanılacağı.

The asChild prop, modern React bileşen kütüphanelerinde güçlü bir kalıptır. Radix UI tarafından popüler hale getirilmiş ve shadcn/ui tarafından benimsenmiştir; bu kalıp, bileşenin işlevselliğini korurken varsayılan işaretlemeyi özel öğelerle değiştirmeyi sağlar.

asChild'ı Anlama

Temelde, asChild bir bileşenin nasıl render edildiğini değiştirir. true olarak ayarlandığında, bileşen varsayılan DOM öğesini render etmek yerine, props'larını, davranışlarını ve event handler'larını doğrudan hemen altındaki çocuk öğe ile birleştirir.

asChild Olmadan

<Dialog.Trigger>
  <button>Open Dialog</button>
</Dialog.Trigger>

Bu iç içe geçmiş öğeler render eder:

<button data-state="closed">
  <button>Open Dialog</button>
</button>

asChild ile

<Dialog.Trigger asChild>
  <button>Open Dialog</button>
</Dialog.Trigger>

Bu tek, birleştirilmiş bir öğe render eder:

<button data-state="closed">Open Dialog</button>

Dialog.Trigger'ın işlevselliği, gereksiz sarmalayıcı öğeleri ortadan kaldırarak butonunuza uygulanır.

Nasıl Çalışır

Alt katta, asChild React'in bileşim yeteneklerini kullanarak bileşenleri birleştirir:

// Simplified implementation
function Component({ asChild, children, ...props }) {
  if (asChild) {
    // Clone child and merge props
    return React.cloneElement(children, {
      ...props,
      ...children.props,
      // Merge event handlers
      onClick: (e) => {
        props.onClick?.(e);
        children.props.onClick?.(e);
      }
    });
  }

  // Render default element
  return <button {...props}>{children}</button>;
}

Bileşen:

  1. asChild'ın true olup olmadığını kontrol eder
  2. Çocuk öğeyi klonlar
  3. Hem ebeveynin hem de çocuğun props'larını birleştirir
  4. Event handler'ları birleştirir
  5. Geliştirilmiş çocuğu döner

Temel Faydalar

1. Semantik HTML

asChild, kullanım durumunuza en uygun HTML öğesini kullanmanıza olanak tanır:

// Use a link for navigation
<AlertDialog.Trigger asChild>
  <a href="/delete">Delete Account</a>
</AlertDialog.Trigger>

// Use a custom button component
<Tooltip.Trigger asChild>
  <IconButton icon={<InfoIcon />} />
</Tooltip.Trigger>

2. Temiz DOM Yapısı

Geleneksel bileşim genellikle derin iç içe geçmiş DOM yapıları oluşturur. asChild bu "sarmalayıcı cehennemi"ni ortadan kaldırır:

// Without asChild: Nested wrappers
<TooltipProvider>
  <Tooltip>
    <TooltipTrigger>
      <button>
        <span>Hover me</span>
      </button>
    </TooltipTrigger>
  </Tooltip>
</TooltipProvider>

// With asChild: Clean structure
<TooltipProvider>
  <Tooltip>
    <TooltipTrigger asChild>
      <button>Hover me</button>
    </TooltipTrigger>
  </Tooltip>
</TooltipProvider>

3. Tasarım Sistemi Entegrasyonu

asChild, mevcut tasarım sistemi bileşenlerinizle sorunsuz entegrasyona olanak tanır:

import { Button } from '@/components/ui/button';

<DropdownMenu.Trigger asChild>
  <Button variant="outline" size="icon">
    <MoreVertical className="h-4 w-4" />
  </Button>
</DropdownMenu.Trigger>

Button bileşeniniz, herhangi bir değişiklik yapmadan gerekli dropdown tetikleyici davranışlarını alır.

4. Bileşen Bileşimi

Birden fazla davranışı tek bir öğe üzerine bileştirebilirsiniz:

<Dialog.Trigger asChild>
  <Tooltip.Trigger asChild>
    <button>
      Open dialog (with tooltip)
    </button>
  </Tooltip.Trigger>
</Dialog.Trigger>

Bu, hem dialog'u açan hem de hover sırasında tooltip gösteren bir buton oluşturur.

Yaygın Kullanım Durumları

Özel Tetikleyici Öğeler

Varsayılan tetikleyicileri özel bileşenlerle değiştirin:

// Custom link trigger
<Collapsible.Trigger asChild>
  <a href="#" className="text-blue-600 underline">
    Toggle Details
  </a>
</Collapsible.Trigger>

// Icon-only trigger
<Popover.Trigger asChild>
  <IconButton>
    <Settings className="h-4 w-4" />
  </IconButton>
</Popover.Trigger>

Erişilebilir Navigasyon

Navigasyon öğeleri için uygun semantiği koruyun:

<NavigationMenu.Link asChild>
  <Link href="/products" className="nav-link">
    Products
  </Link>
</NavigationMenu.Link>

Form Entegrasyonu

Form kütüphaneleri ile entegrasyon sağlarken işlevselliği koruyun:

<FormField
  control={form.control}
  name="acceptTerms"
  render={({ field }) => (
    <FormItem>
      <Checkbox.Root asChild>
        <input
          type="checkbox"
          {...field}
          className="sr-only"
        />
      </Checkbox.Root>
    </FormItem>
  )}
/>

En İyi Uygulamalar

1. Erişilebilirliği Koru

Öğe türünü değiştirirken erişilebilirliğin korunduğundan emin olun:

// ✅ Good - maintains button semantics
<Dialog.Trigger asChild>
  <button type="button">Open</button>
</Dialog.Trigger>

// ⚠️ Dikkat - uygun ARIA özniteliklerini sağlayın
<Dialog.Trigger asChild>
  <div role="button" tabIndex={0}>Open</div>
</Dialog.Trigger>

2. Bileşen Gereksinimlerini Dokümante Edin

Bileşenlerin asChild'ı ne zaman desteklediğini açıkça belgeleyin:

interface TriggerProps {
  /**
   * Change the default rendered element for the one passed as a child,
   * merging their props and behavior.
   *
   * @default false
   */
  asChild?: boolean;
  children: React.ReactNode;
}

3. Çocuk Bileşenleri Test Edin

Özel bileşenlerin asChild ile doğru çalıştığını doğrulayın:

// Test that props are properly forwarded
const TestButton = (props) => {
  console.log('Received props:', props);
  return <button {...props} />;
};

<Tooltip.Trigger asChild>
  <TestButton>Test</TestButton>
</Tooltip.Trigger>

4. Kenar Durumlarını Ele Alın

Koşullu renderlama gibi kenar durumlarını düşünün:

// Handle conditional children
<Dialog.Trigger asChild>
  {isLoading ? (
    <Skeleton className="h-10 w-20" />
  ) : (
    <Button>Open Dialog</Button>
  )}
</Dialog.Trigger>

Yaygın Tuzaklar

Props'ları Yaymamak

Types sayfasında tartışıldığı gibi, props'ları altta yatan öğeye her zaman yaymalısınız.

// ❌ Won't receive trigger behavior
const BadButton = ({ children }) => <button>{children}</button>;

// ✅ Properly receives all props
const GoodButton = ({ children, ...props }) => (
  <button {...props}>{children}</button>
);

Birden Fazla Çocuk

asChild'ı destekleyen bir bileşene birden fazla çocuk geçirmeyin. Bu, bileşenin hangi çocuğu kullanacağını bilmemesi nedeniyle hata oluşturur.

// ❌ Error - asChild expects single child
<Trigger asChild>
  <button>One</button>
  <button>Two</button>
</Trigger>

// ✅ Single child element
<Trigger asChild>
  <button>Single Button</button>
</Trigger>

Fragment Çocukları

asChild'ı destekleyen bir bileşene fragment geçirmeyin. Fragmentler geçerli bir element olmadığından hata oluşur.

// ❌ Fragment is not a valid element
<Trigger asChild>
  <>Button</>
</Trigger>

// ✅ Actual element
<Trigger asChild>
  <button>Button</button>
</Trigger>