React Lifecycle ve Hooks Karşılaştırması

React Lifecycle ve Hooks Karşılaştırması
React bileşenlerinin bir yaşam döngüsü vardır: oluşturulur (mount), güncellenir (update) ve kaldırılır (unmount). Class component'lerde bu aşamalar açık lifecycle metotları ile yönetilirken, fonksiyonel component'lerde Hooks bu görevi üstlenmiştir. Bu yazıda her iki yaklaşımı karşılaştıracak, lifecycle metotlarının hook karşılıklarını gösterecek ve modern React'te yaşam döngüsü yönetiminin en iyi uygulamalarını ele alacağız.
Class Component Lifecycle Metotları
Class component'ler üç ana aşamada çalışan lifecycle metotlarına sahiptir:
Mounting (Oluşturma) Aşaması
constructor()- State başlatma, bind işlemleristatic getDerivedStateFromProps()- Props'tan state türetme (nadiren kullanılır)render()- JSX döndürmecomponentDidMount()- DOM erişimi, API çağrıları, abonelikler
Updating (Güncelleme) Aşaması
static getDerivedStateFromProps()shouldComponentUpdate()- Render optimizasyonurender()getSnapshotBeforeUpdate()- DOM güncellenmeden önce bilgi yakalamacomponentDidUpdate()- Güncelleme sonrası işlemler
Unmounting (Kaldırma) Aşaması
componentWillUnmount()- Temizleme işlemleri
import React, { Component } from 'react';
class DataFetcher extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
loading: true,
error: null
};
this.controller = new AbortController();
}
componentDidMount() {
// Bileşen DOM'a eklendikten sonra veri çek
this.fetchData();
}
componentDidUpdate(prevProps) {
// Props değiştiyse veriyi yeniden çek
if (prevProps.url !== this.props.url) {
this.controller.abort();
this.controller = new AbortController();
this.fetchData();
}
}
componentWillUnmount() {
// Bileşen kaldırılmadan önce temizlik yap
this.controller.abort();
}
async fetchData() {
try {
this.setState({ loading: true });
const response = await fetch(this.props.url, {
signal: this.controller.signal
});
const data = await response.json();
this.setState({ data, loading: false });
} catch (error) {
if (error.name !== 'AbortError') {
this.setState({ error: error.message, loading: false });
}
}
}
render() {
const { data, loading, error } = this.state;
if (loading) return <p>Yükleniyor...</p>;
if (error) return <p>Hata: {error}</p>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
}
Hooks ile Aynı Davranış
import { useState, useEffect } from 'react';
function DataFetcher({ url }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url, { signal: controller.signal });
const result = await response.json();
setData(result);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
}
};
fetchData();
// Cleanup: componentWillUnmount + önceki effect temizliği
return () => controller.abort();
}, [url]); // url değiştiğinde yeniden çalış
if (loading) return <p>Yükleniyor...</p>;
if (error) return <p>Hata: {error}</p>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
Birebir Karşılık Tablosu
| Lifecycle Metodu | Hook Karşılığı | Açıklama |
|---|---|---|
| constructor | useState(initialValue) | State başlatma |
| componentDidMount | useEffect(() => {}, []) | Mount sonrası çalışma |
| componentDidUpdate | useEffect(() => {}, [deps]) | Güncelleme sonrası çalışma |
| componentWillUnmount | useEffect return fonksiyonu | Temizleme işlemleri |
| shouldComponentUpdate | React.memo + useMemo | Render optimizasyonu |
| getDerivedStateFromProps | Render sırasında hesaplama | Props'tan türetilen değerler |
Hooks ile lifecycle arasındaki karşılıklar birebir değildir. useEffect, lifecycle metotlarının doğrudan bir yerine geçmesi olarak tasarlanmamıştır. Bunun yerine "yan etkilerin senkronizasyonu" zihinsel modeliyle çalışır.
Önemli Fark: Zihinsel Model
// Class: "Mount olduğunda X yap, update olduğunda Y yap, unmount olduğunda Z yap"
// 3 farklı lifecycle metodu, 3 farklı mantık yeri
// Hooks: "Bu değerler değiştiğinde, bu effect'i senkronize et"
// İlişkili mantık tek bir yerde
useEffect(() => {
// Setup: Bağlantı kur
const connection = createConnection(roomId);
connection.connect();
// Cleanup: Bağlantıyı kapat
return () => connection.disconnect();
}, [roomId]); // roomId değiştiğinde yeniden senkronize et
Bilgi: Class component'lerde ilişkili mantık farklı lifecycle metotlarına dağılır (örneğin componentDidMount'ta subscribe, componentWillUnmount'ta unsubscribe). Hooks'ta ise ilişkili mantık aynı useEffect içinde tutularak kod tutarlılığı artar.
shouldComponentUpdate vs React.memo
// Class: shouldComponentUpdate
class ExpensiveList extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.items !== this.props.items;
}
render() {
return this.props.items.map(item => <div key={item.id}>{item.name}</div>);
}
}
// Hooks: React.memo
const ExpensiveList = memo(function ExpensiveList({ items }) {
return items.map(item => <div key={item.id}>{item.name}</div>);
});
// Özel karşılaştırma fonksiyonu ile
const ExpensiveList = memo(function ExpensiveList({ items, onSelect }) {
return items.map(item => (
<div key={item.id} onClick={() => onSelect(item.id)}>{item.name}</div>
));
}, (prevProps, nextProps) => {
// true döndürmek = re-render YAPMA
return prevProps.items === nextProps.items;
});
getSnapshotBeforeUpdate: Hook Karşılığı Var mı?
// Class: getSnapshotBeforeUpdate - DOM güncellenmeden önce bilgi yakalar
class ChatLog extends Component {
getSnapshotBeforeUpdate(prevProps) {
if (prevProps.messages.length < this.props.messages.length) {
const container = this.containerRef.current;
return container.scrollHeight - container.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
const container = this.containerRef.current;
container.scrollTop = container.scrollHeight - snapshot;
}
}
}
// Hooks'ta doğrudan karşılığı yoktur
// useLayoutEffect ile benzer sonuç elde edilebilir
function ChatLog({ messages }) {
const containerRef = useRef(null);
const prevScrollHeightRef = useRef(0);
useLayoutEffect(() => {
const container = containerRef.current;
if (container) {
const isNearBottom =
container.scrollHeight - container.scrollTop - container.clientHeight < 50;
if (isNearBottom) {
container.scrollTop = container.scrollHeight;
}
}
}, [messages]);
return (
<div ref={containerRef} style={{ overflowY: 'auto', maxHeight: '300px' }}>
{messages.map(msg => <p key={msg.id}>{msg.text}</p>)}
</div>
);
}
Uyarı: useLayoutEffect, DOM güncellemesi tarayıcıya yansıtılmadan önce senkron olarak çalışır. Bu, scroll pozisyonu düzeltme gibi görsel tutarsızlıkları önlemek için gereklidir, ancak kötüye kullanımı performansı olumsuz etkiler. Çoğu durumda useEffect yeterlidir.
Sonuç
React'in lifecycle metotlarından Hooks'a geçişi, sadece bir sözdizimi değişikliği değil, düşünce modelinde bir paradigma değişikliğidir. Lifecycle metotları "ne zaman" sorusuna odaklanırken, Hooks "neyle senkronize et" sorusuna odaklanır. Her iki sistemi de anlamak, eski kod tabanlarında çalışırken ve modern React geliştirmede size avantaj sağlar. Yeni projelerde Hooks kullanmayı tercih edin, ancak Error Boundary gibi henüz hook karşılığı olmayan özellikler için class component'leri bilmek hala önemlidir.