React’te Sınıf Bileşenlerine Genel Bakış
React, modern web uygulamaları geliştirmek için kullanılan güçlü bir JavaScript kütüphanesidir. Bileşen tabanlı mimarisi sayesinde karmaşık kullanıcı arayüzlerini yönetilebilir, yeniden kullanılabilir parçalara ayırmayı kolaylaştırır. React’in ilk zamanlarından itibaren temel yapı taşlarından biri olan sınıf bileşenleri (Class Components), özellikle uygulamanın durumu (state) ve yaşam döngüsü (lifecycle) yönetimi konusunda merkezi bir rol oynamıştır. Her ne kadar React Hooks’un ortaya çıkışıyla fonksiyonel bileşenler daha popüler hale gelmiş olsa da, sınıf bileşenlerini anlamak, React’in temel prensiplerini kavramak, eski kod tabanlarını sürdürmek veya hata sınırları (error boundaries) gibi özel durumları ele almak için hala büyük önem taşımaktadır.
Bu makalede, React sınıf bileşenlerinin ne olduğunu, nasıl çalıştığını, temel yapılarını, durum ve özellik (props) yönetimini, yaşam döngüsü metotlarını, olay yönetimini, referansları ve Context API ile kullanımını detaylı bir şekilde inceleyeceğiz. Ayrıca, sınıf bileşenlerinin avantaj ve dezavantajlarını tartışarak, modern React geliştirme dünyasındaki yerini değerlendireceğiz.
Sınıf Bileşenlerinin Temel Yapısı
React’te bir sınıf bileşeni oluşturmak için ES6 (ECMAScript 2015) sınıf sözdizimini kullanırız. Temel olarak, React.Component sınıfını genişleten bir JavaScript sınıfıdır. Bu miras alma işlemi, bileşenimize React’in sunduğu özel yetenekleri (state, lifecycle metotları vb.) kazandırır.
Bileşen Tanımlama
Bir sınıf bileşeninin en temel yapısı şu şekildedir:
import React, { Component } from 'react';
class MyComponent extends Component {
render() {
return (
Bu benim render metodumdan dönen içerik.
);
}
}
export default MyComponent;
Yukarıdaki örnekte:
* import React, { Component } from 'react';: React kütüphanesini ve Component sınıfını içe aktarıyoruz. Component sınıfı, React.Component yerine daha kısa bir yazım sağlar.
* class MyComponent extends Component { ... }: MyComponent adında yeni bir sınıf tanımlıyoruz ve bu sınıfı Component sınıfından türetiyoruz.
* render() { ... }: Bu metot, sınıf bileşenlerinin mutlaka sahip olması gereken tek metottur. Bileşenin kullanıcı arayüzünde ne göstereceğini belirten JSX (JavaScript XML) kodunu döndürmekle sorumludur. render() metodu her çağrıldığında (genellikle durum veya özellikler değiştiğinde), React sanal DOM’u günceller ve gerekli değişiklikleri gerçek DOM’a yansıtır.
* export default MyComponent;: Bileşenimizin başka dosyalardan içe aktarılabilmesini sağlar.
Durum (State) Yönetimi
Durum (state), bir bileşenin zaman içinde değişebilen ve kullanıcı arayüzünü etkileyen verilerini temsil eder. Sınıf bileşenlerinde durum, bileşenin kendi içinde yönetilir ve bileşenin yaşam döngüsü boyunca değişebilir.
Başlangıç Durumu Tanımlama
Bir sınıf bileşeninin başlangıç durumunu tanımlamak için constructor metodunu kullanırız. constructor metodu, bir sınıfın bir örneği oluşturulduğunda ilk çağrılan özel bir metottur.
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props); // Üst sınıfın yapılandırıcısını çağırmak zorunludur.
this.state = {
count: 0,
message: "Sayıcı Uygulaması"
};
}
render() {
return (
{this.state.message}
Mevcut Sayı: {this.state.count}
);
}
}
export default Counter;
* constructor(props): Yapılandırıcı metot, bileşen oluşturulduğunda çalışır. props argümanını alır.
* super(props): Bu çağrı, türetilen sınıfın yapılandırıcısında this anahtar kelimesini kullanmadan önce mutlaka yapılmalıdır. Üst sınıfın (Component) yapılandırıcısını çağırır ve bileşenin props‘larını doğru şekilde başlatır.
* this.state = { ... };: Bileşenin başlangıç durumunu tanımladığımız yerdir. state, her zaman bir JavaScript nesnesi olmalıdır.
Durumu Güncelleme: this.setState()
Durumu doğrudan this.state.count = 1; gibi atamalarla asla değiştirmemelisiniz. Durumu güncellemek için her zaman this.setState() metodunu kullanmalısınız. this.setState(), React’e bileşenin durumunun değiştiğini ve arayüzün yeniden render edilmesi gerektiğini bildirir.
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
// Metotları bağlama (binding) - detaylar olay yönetimi bölümünde
this.increment = this.increment.bind(this);
}
increment() {
// this.setState() asenkron çalışır ve durum nesnesini birleştirir (merge).
// Önceki duruma bağlı güncellemeler için fonksiyonel formunu kullanmak daha güvenlidir.
this.setState((prevState) => ({
count: prevState.count + 1
}));
}
render() {
return (
Mevcut Sayı: {this.state.count}
);
}
}
export default Counter;
* this.setState(updater, [callback]): İki şekilde kullanılabilir:
* Nesne ile: this.setState({ count: this.state.count + 1 }). Bu form, güncellemeler arasında çakışmalar olabileceğinden, mevcut duruma bağlı güncellemeler için önerilmez.
* Fonksiyon ile: this.setState((prevState, props) => ({ count: prevState.count + 1 })). Bu, önceki duruma ve özelliklere güvenli bir şekilde erişmenizi sağlar ve durum güncellemelerinin sırasını garanti eder. React, birden fazla setState çağrısını tek bir güncellemede birleştirebilir (batching).
* this.setState() asenkron çalışır. Yani, setState çağrıldıktan hemen sonra this.state‘in güncellenmiş değeri garanti edilmez. Güncelleme tamamlandıktan sonra bir işlem yapmak isterseniz, ikinci argüman olarak bir geri çağırma (callback) fonksiyonu sağlayabilirsiniz: this.setState({ count: 1 }, () => console.log('Durum güncellendi:', this.state.count));
Özellikler (Props) Kullanımı
Özellikler (props), bir üst bileşenden alt bileşenlere veri aktarmak için kullanılır. Props’lar “özellikler” anlamına gelir ve bir bileşenin dışarıdan aldığı yapılandırma parametreleri gibidir. Props’lar salt okunurdur (read-only) ve alt bileşenler tarafından değiştirilemez.
// ParentComponent.js
import React, { Component } from 'react';
import ChildComponent from './ChildComponent';
class ParentComponent extends Component {
render() {
const userName = "Alice";
const userAge = 30;
return (
);
}
}
export default ParentComponent;
// ChildComponent.js
import React, { Component } from 'react';
class ChildComponent extends Component {
render() {
// Props'lara this.props üzerinden erişilir.
const { name, age } = this.props;
return (
Çocuk Bileşen
Adı: {name}
Yaş: {age}
);
}
}
export default ChildComponent;
* Üst bileşen (ParentComponent), ChildComponent‘e name ve age adında iki özellik (prop) iletiyor.
* Alt bileşen (ChildComponent) içinde, bu özelliklere this.props.name ve this.props.age şeklinde erişilir.
* Props’lar değiştirilemez; this.props.name = "Yeni İsim"; gibi bir atama hata verir veya istenmeyen davranışlara yol açar.
defaultProps ve propTypes
* defaultProps: Bir bileşenin belirli özellikler için varsayılan değerlere sahip olmasını sağlar. Eğer üst bileşen ilgili özelliği sağlamazsa, varsayılan değer kullanılır.
class ChildComponent extends Component {
// ...
}
ChildComponent.defaultProps = {
name: "Misafir",
age: 0
};
* propTypes: Geliştirme aşamasında özelliklerin beklenen veri tiplerini doğrulamak için kullanılır. React’in kendi prop-types kütüphanesi ile birlikte kullanılır. Üretim ortamında performans amacıyla kaldırılır.
import PropTypes from 'prop-types';
class ChildComponent extends Component {
// ...
}
ChildComponent.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number
};
Bu, name prop’unun bir string olmasını ve zorunlu olmasını, age prop’unun ise bir sayı olmasını beklediğimizi belirtir.
Yaşam Döngüsü Metotları
React sınıf bileşenleri, yaşam döngülerinin belirli aşamalarında çalışan özel metotlara sahiptir. Bu metotlar, bileşenin oluşturulması, güncellenmesi ve DOM’dan kaldırılması gibi olaylara tepki vermemizi sağlar.
Birleşme (Mounting) Aşaması
Bileşenin ilk kez DOM’a eklenmesi sürecidir.
1. constructor(props): İlk çağrılır. Başlangıç durumunu ve metotları bağlamak için kullanılır.
2. static getDerivedStateFromProps(props, state): Nadiren kullanılır. render metodu çağrılmadan hemen önce, hem birleşme hem de güncelleme aşamalarında çağrılır. Props’taki değişikliklere göre state’i güncellemek için kullanılır. Bir nesne döndürmelidir (state’i güncellemek için) veya null döndürmelidir (state’i değiştirmemek için).
3. render(): JSX döndürür. Bileşenin görünümünü oluşturur.
4. componentDidMount(): Bileşen DOM’a eklendikten ve render edildikten hemen sonra çağrılır. Ağ istekleri (API çağrıları), DOM manipülasyonları, abonelikler (subscriptions) veya zamanlayıcılar (timers) gibi yan etkileri (side effects) burada başlatmak için idealdir.
class DataFetcher extends Component {
constructor(props) {
super(props);
this.state = { data: null, loading: true };
}
componentDidMount() {
console.log('Bileşen DOM\'a eklendi.');
// Veri çekme işlemi
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => this.setState({ data, loading: false }))
.catch(error => console.error('Veri çekme hatası:', error));
}
render() {
if (this.state.loading) {
return Veriler yükleniyor...
;
}
return Veri: {JSON.stringify(this.state.data)};
}
}
Güncelleme (Updating) Aşaması
Bileşenin props veya state’i değiştiğinde gerçekleşir.
1. static getDerivedStateFromProps(props, state): Props veya state değiştiğinde tekrar çağrılır.
2. shouldComponentUpdate(nextProps, nextState): Performans optimizasyonu için kullanılır. Varsayılan olarak true döndürür. Eğer false döndürürseniz, bileşenin yeniden render edilmesini ve alt bileşenlerinin güncellenmesini engeller. Genellikle, props veya state’in gerçekten değişip değişmediğini kontrol etmek için kullanılır.
shouldComponentUpdate(nextProps, nextState) {
// Sadece 'count' prop'u değişirse yeniden render et.
if (nextProps.count !== this.props.count) {
return true;
}
return false;
}
3. render(): Eğer shouldComponentUpdate true dönerse, bileşen yeniden render edilir.
4. getSnapshotBeforeUpdate(prevProps, prevState): Nadiren kullanılır. render metodu çağrıldıktan hemen sonra, ancak DOM’a değişiklikler yansıtılmadan önce çağrılır. DOM’un güncellenmeden önceki durumunu yakalamak için kullanılır (örneğin, bir kaydırma pozisyonunu kaydetmek). Bu metodun döndürdüğü değer, componentDidUpdate metoduna üçüncü argüman olarak iletilir.
5. componentDidUpdate(prevProps, prevState, snapshot): Bileşen güncellendikten ve DOM’a yansıtıldıktan hemen sonra çağrılır. Güncellenen DOM ile etkileşime girmek, ağ istekleri yapmak (props’lar değiştiğinde) veya önceki props/state ile mevcut props/state’i karşılaştırmak için kullanılır.
componentDidUpdate(prevProps, prevState) {
if (this.props.userId !== prevProps.userId) {
console.log('Kullanıcı ID değişti, yeni veri çekiliyor...');
// Yeni kullanıcı verilerini çekme
// fetchUser(this.props.userId);
}
}
Ayrılma (Unmounting) Aşaması
Bileşenin DOM’dan kaldırılması sürecidir.
1. componentWillUnmount(): Bileşen DOM’dan kaldırılmadan hemen önce çağrılır. Bu metot, componentDidMount veya componentDidUpdate içinde başlatılan abonelikleri iptal etmek, zamanlayıcıları temizlemek veya DOM üzerinde yapılan manuel değişiklikleri geri almak gibi temizlik işlemleri için kullanılır. Bellek sızıntılarını önlemek için kritik öneme sahiptir.
class Timer extends Component {
constructor(props) {
super(props);
this.state = { seconds: 0 };
this.timer = null;
}
componentDidMount() {
this.timer = setInterval(() => {
this.setState(prevState => ({
seconds: prevState.seconds + 1
}));
}, 1000);
}
componentWillUnmount() {
console.log('Bileşen kaldırılıyor, zamanlayıcı temizleniyor.');
clearInterval(this.timer); // Bellek sızıntısını önle
}
render() {
return Geçen süre: {this.state.seconds} saniye
;
}
}
Hata Yönetimi (Error Handling)
React 16 ile birlikte tanıtılan hata sınırları (error boundaries), alt bileşen ağacındaki JavaScript hatalarını yakalamak, loglamak ve kullanıcıya yedek bir UI göstermek için kullanılır.
1. static getDerivedStateFromError(error): Bir alt bileşende hata oluştuğunda çağrılır. Hata durumunu güncellemek için bir nesne döndürmelidir.
2. componentDidCatch(error, errorInfo): Bir alt bileşende hata oluştuğunda çağrılır. Hata bilgilerini bir loglama hizmetine göndermek gibi yan etkiler için kullanılır.
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Bir sonraki render'da yedek UI gösterebilmek için state'i güncelleyin.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Hata raporlama hizmetine hata bilgisini loglayabilirsiniz.
console.error("ErrorBoundary yakaladı:", error, errorInfo);
}
render() {
if (this.state.hasError) {
// Yedek UI'yı render edebilirsiniz.
return ;
}
return this.props.children; // Normalde çocukları render et.
}
}
Olay Yönetimi (Event Handling)
React’te olaylar, DOM olaylarına benzer şekilde işlenir ancak bazı farklılıklar vardır. React, tarayıcılar arası uyumluluğu sağlamak için “sentetik olaylar” (synthetic events) kullanır.
import React, { Component } from 'react';
class EventExample extends Component {
constructor(props) {
super(props);
this.state = { message: "Merhaba!" };
// Metodu bağlama (binding)
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ message: "Butona tıklandı!" });
console.log(this); // 'this' artık bileşeni işaret eder
}
handleInputChange = (event) => { // Sınıf özelliği (arrow function) ile binding
this.setState({ message: event.target.value });
};
render() {
return (
{this.state.message}
{/ Yöntem 1: Constructor'da binding /}
{/ Yöntem 2: Render içinde arrow function - performans maliyeti olabilir /}
{/ Yöntem 3: Sınıf özelliği (arrow function) ile binding - en yaygın ve önerilen /}
);
}
}
export default EventExample;
* this bağlama (binding): JavaScript’te sınıf metotları varsayılan olarak this bağlamını korumaz. React olay işleyicilerinde this‘in bileşeni işaret etmesini sağlamak için üç yaygın yöntem vardır:
1. constructor içinde .bind(this) kullanmak: En geleneksel yöntemdir. Her örnek için bir kez yapılır.
2. Render metodu içinde ok fonksiyonu (arrow function) kullanmak: onClick={() => this.handleClick()}. Her render edildiğinde yeni bir fonksiyon oluşturulduğu için performans maliyeti olabilir, ancak basit durumlar için kabul edilebilir.
3. Sınıf özelliği olarak ok fonksiyonu tanımlamak: handleClick = () => { ... }. Babel eklentisi gerektirse de (CRA gibi araçlar bunu varsayılan olarak destekler), en temiz ve önerilen yöntemdir.
* Sentetik Olaylar: React, tarayıcıların yerel olaylarını saran sentetik olay nesneleri kullanır. Bu nesneler, farklı tarayıcılarda tutarlı davranış sağlar. event.preventDefault() veya event.stopPropagation() gibi standart DOM olay metotları sentetik olay nesnelerinde de mevcuttur.
Referanslar (Refs)
React’in deklaratif yapısı gereği, genellikle doğrudan DOM elemanlarına veya bileşen örneklerine erişmeye gerek kalmaz. Ancak bazı özel durumlarda bu tür bir erişim gerekli olabilir. Referanslar (refs), bu doğrudan erişimi sağlamak için kullanılır.
* Ne zaman kullanılır?
* Bir form girişine odaklanmak (focus) veya metin seçmek.
* Medya oynatmayı yönetmek (oynat, duraklat).
* Animasyonları tetiklemek.
* Üçüncü taraf DOM kütüphaneleriyle entegrasyon.
* Ne zaman kullanılmamalıdır?
* Props veya state ile yönetilebilecek her durumda.
* Deklaratif React akışını bozmaktan kaçının.
import React, { Component } from 'react';
class RefExample extends Component {
constructor(props) {
super(props);
this.myInputRef = React.createRef(); // Ref oluşturma
}
componentDidMount() {
// Bileşen DOM'a eklendikten sonra input'a odaklan
this.myInputRef.current.focus();
}
handleClick = () => {
alert(Input değeri: ${this.myInputRef.current.value});
};
render() {
return (
{/ Ref'i elemana bağlama /}
);
// this.myInputRef.current, render edildikten sonra DOM elemanını işaret eder.
}
}
export default RefExample;
* React.createRef(): Yapılandırıcıda bir ref oluştururuz.
* ref={this.myInputRef}: Oluşturulan ref’i JSX elemanına bağlarız.
* this.myInputRef.current: Bileşen DOM’a eklendikten sonra, bağlı olduğu DOM elemanına veya bileşen örneğine .current özelliği üzerinden erişilir.
Context API ile Kullanım
Context API, prop drilling (özellikleri birçok alt bileşenden geçirme) sorununu çözmek için React 16.3 ile tanıtıldı. Sınıf bileşenlerinde Context’i kullanmanın iki ana yolu vardır:
1. Context.Consumer bileşeni: JSX içinde bir render prop deseni kullanarak Context değerine erişim sağlar.
// MyContext.js
import React from 'react';
export const ThemeContext = React.createContext('light'); // Varsayılan değer
// App.js
import React, { Component } from 'react';
import { ThemeContext } from './MyContext';
import ThemedButton from './ThemedButton';
class App extends Component {
render() {
return (
);
}
}
// ThemedButton.js
import React, { Component } from 'react';
import { ThemeContext } from './MyContext';
class ThemedButton extends Component {
render() {
return (
{theme => (
)}
);
}
}
2. static contextType: Sadece tek bir Context’e erişmek isteyen sınıf bileşenleri için daha basit bir yöntemdir.
// ThemedButton.js (static contextType ile)
import React, { Component } from 'react';
import { ThemeContext } from './MyContext';
class ThemedButton extends Component {
static contextType = ThemeContext; // Sadece tek bir context bağlanabilir.
render() {
const theme = this.context; // this.context üzerinden erişim
return (
);
}
}
Sınıf Bileşenlerinin Avantajları ve Dezavantajları
React’in evrimiyle birlikte, sınıf bileşenlerinin bazı avantajları ve dezavantajları daha belirgin hale gelmiştir.
Avantajlar
* Durum ve Yaşam Döngüsü Yönetimi: this.state ve yaşam döngüsü metotları (componentDidMount, componentDidUpdate, componentWillUnmount) sayesinde bileşenin tüm yaşam döngüsü üzerinde tam kontrol sağlar.
* Hata Sınırları (Error Boundaries): Sadece sınıf bileşenleri componentDidCatch ve static getDerivedStateFromError metotları ile hata sınırları oluşturabilir, bu da uygulamanın geri kalanını çökmeden korumak için hayati öneme sahiptir.
* Eski Kod Tabanları: Mevcut birçok büyük ölçekli React uygulaması hala sınıf bileşenleri kullanmaktadır. Bu kod tabanlarını anlamak ve sürdürmek için sınıf bileşeni bilgisi gereklidir.
Dezavantajlar
* Boilerplate Kodu: Durum yönetimi, metot bağlama ve yaşam döngüsü metotları nedeniyle fonksiyonel bileşenlere göre daha fazla standart kod (boilerplate) gerektirir.
* this Bağlamının Karmaşıklığı: JavaScript’teki this anahtar kelimesinin davranışı, özellikle olay işleyicilerinde, yeni başlayanlar için kafa karıştırıcı olabilir ve manuel bağlama gerektirir.
* Mantık Tekrarı ve Paylaşımı Zorluğu: Bileşenler arasında durum bilgisi olmayan mantığı (stateless logic) veya durum bilgisi olan mantığı (stateful logic) paylaşmak, Yüksek Dereceli Bileşenler (Higher-Order Components – HOCs) veya Render Props gibi daha karmaşık desenler gerektirir.
* Okunabilirlik ve Test Edilebilirlik: Yaşam döngüsü metotları, ilgili mantığı farklı yerlere dağıtabilir (örneğin, bir veri çekme işlemi componentDidMount ve componentDidUpdate içinde olabilir), bu da kodun okunabilirliğini ve test edilebilirliğini zorlaştırabilir.
* Hooks’un Gölgesinde Kalması: React Hooks’un tanıtılmasıyla (useState, useEffect vb.), fonksiyonel bileşenler artık durum ve yaşam döngüsü yeteneklerine sahip oldu. Bu, fonksiyonel bileşenleri daha özlü, okunabilir ve yeniden kullanılabilir hale getirdi.
Sınıf Bileşenlerinden Fonksiyonel Bileşenlere Geçiş
React Hooks’un 2018’de tanıtılmasıyla, fonksiyonel bileşenler artık sınıf bileşenlerinin tüm yeteneklerine (hatta daha fazlasına) sahip olmuştur. Yeni projelerde genellikle fonksiyonel bileşenler ve Hooks tercih edilir.
* this.state yerine useState.
* componentDidMount, componentDidUpdate, componentWillUnmount yaşam döngüsü metotlarının eşdeğeri olarak useEffect.
* this.context veya Context.Consumer yerine useContext.
* React.createRef() yerine useRef.
Bu geçiş, kodun daha az karmaşık, daha modüler ve daha kolay test edilebilir olmasını sağlar. Ancak, sınıf bileşenlerinin temelini anlamak, React ekosistemini ve eski projeleri anlamak için hala vazgeçilmezdir.
Sonuç
React sınıf bileşenleri, kütüphanenin tarihsel gelişiminde ve modern uygulamaların temelinde önemli bir yere sahiptir. Durum yönetimi, yaşam döngüsü metotları ve hata sınırları gibi güçlü özellikleriyle, karmaşık UI’lar oluşturmak için yıllarca tercih edilen yöntem olmuştur. Her ne kadar React Hooks’un getirdiği yeniliklerle fonksiyonel bileşenler daha popüler hale gelmiş olsa da, sınıf bileşenlerinin prensiplerini ve çalışma şekillerini kavramak, bir React geliştiricisi için hala temel bir beceridir.
Eski kod tabanlarını sürdürmek, hata sınırları oluşturmak veya React’in dahili işleyişini daha iyi anlamak için sınıf bileşenleri bilgisi kritik öneme sahiptir. Yeni projelerde genellikle fonksiyonel bileşenler tercih edilse de, sınıf bileşenleri React’in güçlü ve esnek yapısının bir kanıtı olarak varlığını sürdürmektedir. Bu makale, sınıf bileşenlerinin kapsamlı bir genel bakışını sunarak, React dünyasındaki yerlerini ve önemlerini vurgulamayı amaçlamıştır.