React Component Yapısı: Fonksiyonel ve Class Component Karşılaştırması

React Component Yapısı: Fonksiyonel ve Class Component Karşılaştırması
React'in temel yapı taşı olan bileşenler (components), kullanıcı arayüzünü bağımsız, yeniden kullanılabilir parçalara ayırmanızı sağlar. React tarihinde iki farklı bileşen yazım yöntemi bulunmaktadır: Class Component'ler ve Fonksiyonel Component'ler. Bu yazıda her iki yaklaşımı derinlemesine inceleyecek, aralarındaki farkları ortaya koyacak ve modern React'te neden fonksiyonel bileşenlerin standart haline geldiğini açıklayacağız.
Fonksiyonel Component'ler
Fonksiyonel bileşenler, adından da anlaşılacağı gibi JavaScript fonksiyonlarıdır. Props'u parametre olarak alır ve JSX döndürür. React 16.8 ile birlikte gelen Hooks sayesinde, artık fonksiyonel bileşenler class bileşenlerin tüm özelliklerini destekler.
// Arrow function ile fonksiyonel bileşen
const UserCard = ({ name, email, avatar, role }) => {
return (
<div className="user-card">
<img src={avatar} alt={`${name} profil fotoğrafı`} />
<div className="user-info">
<h3>{name}</h3>
<p>{email}</p>
<span className={`badge badge-${role}`}>{role}</span>
</div>
</div>
);
};
// function declaration ile fonksiyonel bileşen
function UserCard({ name, email, avatar, role }) {
return (
<div className="user-card">
<img src={avatar} alt={`${name} profil fotoğrafı`} />
<div className="user-info">
<h3>{name}</h3>
<p>{email}</p>
<span className={`badge badge-${role}`}>{role}</span>
</div>
</div>
);
}
İpucu: Arrow function ve function declaration arasındaki seçim büyük ölçüde kişisel tercihe bağlıdır. Ancak arrow function kullanmak, bileşenin yanlışlıkla this bağlamına erişmesini engeller ve daha tutarlı bir kod stili sağlar.
Class Component'ler
Class component'ler, React.Component sınıfını extend ederek oluşturulur. Hooks öncesi dönemde state yönetimi ve lifecycle metotları yalnızca class bileşenlerde kullanılabiliyordu:
import React, { Component } from 'react';
class UserProfile extends Component {
constructor(props) {
super(props);
this.state = {
isFollowing: false,
followerCount: props.initialFollowers || 0
};
// Event handler'ları bind etmek gerekir
this.handleFollow = this.handleFollow.bind(this);
}
handleFollow() {
this.setState(prevState => ({
isFollowing: !prevState.isFollowing,
followerCount: prevState.isFollowing
? prevState.followerCount - 1
: prevState.followerCount + 1
}));
}
componentDidMount() {
console.log('Bileşen DOM\'a eklendi');
}
componentDidUpdate(prevProps, prevState) {
if (prevState.isFollowing !== this.state.isFollowing) {
console.log('Takip durumu değişti:', this.state.isFollowing);
}
}
componentWillUnmount() {
console.log('Bileşen DOM\'dan kaldırılacak');
}
render() {
const { name, bio } = this.props;
const { isFollowing, followerCount } = this.state;
return (
<div className="user-profile">
<h2>{name}</h2>
<p>{bio}</p>
<p>{followerCount} takipçi</p>
<button onClick={this.handleFollow}>
{isFollowing ? 'Takibi Bırak' : 'Takip Et'}
</button>
</div>
);
}
}
Detaylı Karşılaştırma
| Özellik | Fonksiyonel Component | Class Component |
|---|---|---|
| Sözdizimi | Basit fonksiyon | ES6 class |
| State yönetimi | useState hook | this.state ve this.setState |
| Lifecycle | useEffect hook | componentDidMount vb. |
| this bağlamı | Yok | Gerekli (bind işlemi) |
| Kod boyutu | Daha kısa | Daha uzun (boilerplate) |
| Performans | Hafif avantaj | Class instance overhead |
| Test edilebilirlik | Daha kolay | Daha karmaşık |
| Kod paylaşımı | Custom Hooks | HOC, Render Props |
Aynı Bileşeni İki Yöntemle Yazma
Farkları net görmek için aynı özelliğe sahip bir bileşeni her iki yöntemle yazalım. Bir API'den veri çeken ve listeleyen bir bileşen örneği:
Fonksiyonel Component Versiyonu
import { useState, useEffect } from 'react';
function ProductList({ category }) {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const fetchProducts = async () => {
try {
setLoading(true);
const response = await fetch(
`/api/products?category=${category}`,
{ signal: controller.signal }
);
if (!response.ok) throw new Error('Veri çekilemedi');
const data = await response.json();
setProducts(data);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
}
};
fetchProducts();
return () => controller.abort();
}, [category]);
if (loading) return <p>Yükleniyor...</p>;
if (error) return <p>Hata: {error}</p>;
return (
<ul>
{products.map(product => (
<li key={product.id}>
<strong>{product.name}</strong> - {product.price} TL
</li>
))}
</ul>
);
}
Class Component Versiyonu
import React, { Component } from 'react';
class ProductList extends Component {
constructor(props) {
super(props);
this.state = {
products: [],
loading: true,
error: null
};
this.controller = null;
}
componentDidMount() {
this.fetchProducts();
}
componentDidUpdate(prevProps) {
if (prevProps.category !== this.props.category) {
this.fetchProducts();
}
}
componentWillUnmount() {
if (this.controller) {
this.controller.abort();
}
}
async fetchProducts() {
if (this.controller) this.controller.abort();
this.controller = new AbortController();
try {
this.setState({ loading: true });
const response = await fetch(
`/api/products?category=${this.props.category}`,
{ signal: this.controller.signal }
);
if (!response.ok) throw new Error('Veri çekilemedi');
const data = await response.json();
this.setState({ products: data, loading: false });
} catch (err) {
if (err.name !== 'AbortError') {
this.setState({ error: err.message, loading: false });
}
}
}
render() {
const { products, loading, error } = this.state;
if (loading) return <p>Yükleniyor...</p>;
if (error) return <p>Hata: {error}</p>;
return (
<ul>
{products.map(product => (
<li key={product.id}>
<strong>{product.name}</strong> - {product.price} TL
</li>
))}
</ul>
);
}
}
Bilgi: Fonksiyonel versiyon yaklaşık %30 daha az kod içerir ve mantığı daha lineer bir şekilde ifade eder. useEffect ile cleanup fonksiyonu, class versiyonundaki üç ayrı lifecycle metodu yerine tek bir yerde tanımlanır.
Bileşen Tasarım İlkeleri
Hangi yöntemi kullanırsanız kullanın, iyi bir React bileşeni tasarlarken şu ilkelere dikkat etmelisiniz:
- Tek Sorumluluk İlkesi: Her bileşen yalnızca bir iş yapmalıdır
- Yeniden Kullanılabilirlik: Bileşenler farklı bağlamlarda kullanılabilecek şekilde tasarlanmalıdır
- Kompozisyon: Küçük bileşenleri birleştirerek karmaşık arayüzler oluşturun
- Props ile Yapılandırma: Bileşen davranışını props ile dışarıdan kontrol edin
- DRY (Don't Repeat Yourself): Tekrarlanan mantığı custom hook'lara veya yardımcı bileşenlere taşıyın
Uyarı: React resmi dökümantasyonu artık yeni projeler için class component kullanımını önermemektedir. Mevcut class component'leri fonksiyonel bileşenlere dönüştürmeniz tavsiye edilir, ancak bu zorunlu değildir. React ekibi class component desteğini kaldırmayı planlamamaktadır.
Sonuç
Modern React geliştirmede fonksiyonel bileşenler ve Hooks, standart yaklaşım haline gelmiştir. Daha az boilerplate kod, daha iyi kod paylaşımı (custom hooks) ve daha kolay test edilebilirlik sağlarlar. Class component'leri anlamak, eski kod tabanlarında çalışırken veya Error Boundary gibi henüz hook karşılığı olmayan özelliklerde hala önemlidir. Yeni projelere fonksiyonel bileşenlerle başlamanız ve Hooks ekosistemini öğrenmeniz önerilir.