React'ta Modern Hooks ve Design Patterns: Kapsamlı Rehber

Yunus Emre Güzel
2 Ocak 202510 dkReact
React'ta Modern Hooks ve Design Patterns: Kapsamlı Rehber

React'ta Modern Hooks ve Design Patterns: Kapsamlı Rehber

React ekosistemi sürekli gelişiyor ve modern web uygulamaları geliştirmek için yeni pattern'lar ve best practice'ler ortaya çıkıyor. Bu yazıda, React uygulamalarınızı daha iyi hale getirecek modern hooks ve design pattern'ları inceleyeceğiz.

Neden Modern Pattern'lar Önemli?

Modern React pattern'ları ve hooks'ları kullanmanın birçok avantajı var:

  • Kod Tekrarını Azaltma: Custom hooks ile ortak mantığı tek bir yerde toplayabilirsiniz
  • Daha İyi Performans: Memoization ve optimizasyon teknikleri ile uygulamanızı hızlandırabilirsiniz
  • Bakımı Kolay Kod: Design pattern'lar ile kodunuzu daha organize ve anlaşılır hale getirebilirsiniz
  • Yeniden Kullanılabilirlik: Modüler ve taşınabilir bileşenler oluşturabilirsiniz

Bu rehberde, bu avantajları nasıl elde edebileceğinizi pratik örneklerle göreceğiz.

Custom Hooks: Yeniden Kullanılabilir Mantık

useLocalStorage Hook'u

Tarayıcı local storage'ını React state ile senkronize eden bir custom hook örneği:

function useLocalStorage<T>(key: string, initialValue: T) {
  // State'i başlat
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  // State değiştiğinde localStorage'ı güncelle
  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(storedValue));
    } catch (error) {
      console.error(error);
    }
  }, [key, storedValue]);

  return [storedValue, setStoredValue] as const;
}

// Kullanım örneği:
function App() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Temayı Değiştir
    </button>
  );
}

useDebounce Hook'u

API çağrıları veya ağır işlemleri optimize etmek için debounce hook'u:

function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]);

  return debouncedValue;
}

// Kullanım örneği:
function SearchComponent() {
  const [search, setSearch] = useState('');
  const debouncedSearch = useDebounce(search, 500);

  useEffect(() => {
    // API çağrısı
    fetchSearchResults(debouncedSearch);
  }, [debouncedSearch]);

  return (
    <input
      type="text"
      value={search}
      onChange={(e) => setSearch(e.target.value)}
    />
  );
}

Modern React Pattern'ları

Compound Components Pattern

Birlikte çalışan, esnek ve yeniden kullanılabilir bileşenler oluşturma:

// Accordion bileşeni örneği
const AccordionContext = createContext<{
  activeIndex: number;
  setActiveIndex: (index: number) => void;
} | null>(null);

function Accordion({ children }: { children: React.ReactNode }) {
  const [activeIndex, setActiveIndex] = useState(0);
  
  return (
    <AccordionContext.Provider value={{ activeIndex, setActiveIndex }}>
      <div className="accordion">{children}</div>
    </AccordionContext.Provider>
  );
}

function AccordionItem({ children, index }: { children: React.ReactNode; index: number }) {
  const context = useContext(AccordionContext);
  if (!context) throw new Error('AccordionItem must be used within Accordion');
  
  const { activeIndex, setActiveIndex } = context;
  const isActive = activeIndex === index;
  
  return (
    <div className="accordion-item">
      {React.Children.map(children, child => {
        if (React.isValidElement(child)) {
          return React.cloneElement(child, { isActive });
        }
        return child;
      })}
    </div>
  );
}

// Kullanım örneği:
function App() {
  return (
    <Accordion>
      <AccordionItem index={0}>
        <AccordionHeader>Başlık 1</AccordionHeader>
        <AccordionContent>İçerik 1</AccordionContent>
      </AccordionItem>
      <AccordionItem index={1}>
        <AccordionHeader>Başlık 2</AccordionHeader>
        <AccordionContent>İçerik 2</AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}

Render Props Pattern

Bileşen mantığını paylaşmanın esnek bir yolu:

interface MousePositionProps {
  render: (position: { x: number; y: number }) => React.ReactNode;
}

function MousePosition({ render }: MousePositionProps) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    function handleMouseMove(e: MouseEvent) {
      setPosition({ x: e.clientX, y: e.clientY });
    }

    window.addEventListener('mousemove', handleMouseMove);
    return () => window.removeEventListener('mousemove', handleMouseMove);
  }, []);

  return <>{render(position)}</>;
}

// Kullanım örneği:
function App() {
  return (
    <MousePosition
      render={({ x, y }) => (
        <div>
          Fare pozisyonu: {x}, {y}
        </div>
      )}
    />
  );
}

Performance Optimizasyonları

useMemo ve useCallback

Gereksiz render'ları önlemek için memoization kullanımı:

function ExpensiveComponent({ data, onItemSelect }: Props) {
  // Ağır hesaplama gerektiren işlemleri memoize et
  const processedData = useMemo(() => {
    return data.map(item => ({
      ...item,
      calculated: expensiveCalculation(item)
    }));
  }, [data]);

  // Event handler'ları memoize et
  const handleSelect = useCallback((item: Item) => {
    onItemSelect(item);
  }, [onItemSelect]);

  return (
    <ul>
      {processedData.map(item => (
        <li key={item.id} onClick={() => handleSelect(item)}>
          {item.calculated}
        </li>
      ))}
    </ul>
  );
}

React.memo ile Bileşen Memoization

Alt bileşenlerin gereksiz render'larını önleme:

interface ItemProps {
  item: Item;
  onSelect: (item: Item) => void;
}

const ListItem = React.memo(function ListItem({ item, onSelect }: ItemProps) {
  return (
    <li onClick={() => onSelect(item)}>
      {item.name}
    </li>
  );
});

// Özel karşılaştırma fonksiyonu ile kullanım
const ListItemWithCustomComparison = React.memo(
  ListItem,
  (prevProps, nextProps) => {
    return prevProps.item.id === nextProps.item.id;
  }
);

Error Boundaries

Hata yönetimi için modern yaklaşım:

class ErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { hasError: boolean }
> {
  constructor(props: { children: React.ReactNode }) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Bir şeyler yanlış gitti.</h1>;
    }

    return this.props.children;
  }
}

// Kullanım örneği:
function App() {
  return (
    <ErrorBoundary>
      <ComponentThatMightError />
    </ErrorBoundary>
  );
}

Sonuç

Modern React geliştirmede, custom hooks ve design pattern'ların doğru kullanımı, kodunuzu daha maintainable ve performanslı hale getirir. Bu yazıda öğrendiğimiz pattern'ları kendi projelerinizde kullanarak:

  • Kod tekrarını azaltabilir
  • Bileşen mantığını daha iyi organize edebilir
  • Performans optimizasyonlarını doğru şekilde uygulayabilir
  • Daha sürdürülebilir uygulamalar geliştirebilirsiniz

Bu pattern'ları ve hooks'ları kullanırken, her zaman use-case'inize en uygun çözümü seçmeye özen gösterin ve gereksiz optimizasyonlardan kaçının.