İçeriğe geç
react

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

Tarık Tunç
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ı

  1. constructor() - State başlatma, bind işlemleri
  2. static getDerivedStateFromProps() - Props'tan state türetme (nadiren kullanılır)
  3. render() - JSX döndürme
  4. componentDidMount() - DOM erişimi, API çağrıları, abonelikler

Updating (Güncelleme) Aşaması

  1. static getDerivedStateFromProps()
  2. shouldComponentUpdate() - Render optimizasyonu
  3. render()
  4. getSnapshotBeforeUpdate() - DOM güncellenmeden önce bilgi yakalama
  5. componentDidUpdate() - Güncelleme sonrası işlemler

Unmounting (Kaldırma) Aşaması

  1. 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.

Kaynaklar

İlgili Yazılar