* * *

Web uygulamaları gün geçtikçe karmaşıklaşmaya ve büyümeye devam ediyor. Bu durum çoğu zaman performans sorunlarını da beraberinde getiriyor. Fakat biz geliştiriciler uygulama ne kadar büyürse büyüsün son kullanıcının ulaşabilirliğini en yüksek seviyede tutmak zorunda. Bu yüzden geliştirdiğimiz uygulama genelinde performans ile ilgili iyileştirmeler yapmalı ve kaynakları en verimli şekilde kullanmalıyız 😕.

Tüm UI işlemlerini javascript'in sırtına yüklediğimiz şu dönemlerde de bu durum fazlasıyla önem arz ediyor. Özellikle React, Vue, Angular gibi araçlar kullanıyorsak kodumuz build olduğunda dosya boyutu büyük bir bundle (kodumuzun tek bir javascript dosyası haline gelmesi 📦) çıktısı alırız. Bu bundle içinde kullanıcının uygulamada geçirdiği süre boyunca hiç etkileşime girmeyeceği kısımlarda bulunacaktır. Bu bundle içinde sadece kullanıcın etkileşime gireceği ve uygulamamızın çalışması için gereken kısımların olması performans açısından uygulamamıza büyük bir fayda sağlayacaktır 💡.

Bir React kodu üzerinde sorunu daha net anlamaya çalışalım.

import React from 'react'

// Components
import Header from './components/Header'
import LoginModal from './components/LoginModal'
import NotificationPopup from './components/NotificationBar'
import Product from './components/Product'
import ProductReviews from './components/Product/Reviews'
import SideMenu from './components/SideMenu'

const App = () => (
  <>
    <Header />
    <SideMenu />
    <NotificationPopup />
    <Product />
    <ProductReviews />
    <LoginModal />
  </>
)

export default App

Yukarıdaki React kodunu incelediğimiz bazı component'ların bundle dosyamızda bulunması çok da mantıklı değil. Örneğin <LoginModal /> component'ı kullanıcı giriş yapmak istemediği sürece kullanılmayacak. Aynı şekilde <ProductReviews /> component'ı kullanıcı ürün yorumları tab'ına tıklamadığı sürece devreye girmeyecek. Bu componentları bundle dosyamızdan bir şekilde kaldırmak ve kullanıcı etkileşime gireceği zaman yüklemek uygulamamızın performansını büyük ölçüde arttıracaktır.

Code Splitting

Kodumuzu ufak parçalara (chunk) bölüp ve bu parçaların uygulamanın çalışma zamanında isteğe göre yüklenmesini sağlamaya code splitting yani kod bölümleme diyoruz. Webpack, Browserify, Rollup gibi araçlar ile bu konfigürasyonu yapmak mümkün. Daha kolayı da var. Eğer Create React App, Gatsby veya Next.js gibi araçlar kullanıyorsanız bu özellik built-in olarak geliyor.

⚠️ Uygulamanızda Client Side Routing yapıyorsanız mutlaka code splitting yapmaya özen göstermelisiniz.

Sorunu anladığımıza ve kısaca code splitting'den bahsettiğimize göre artık React tarafında bunu hallediyoruz kısmına geçebiliriz.

React.lazy()

Component'larımızı dinamik olarak yüklemeyi sağlayan metod React.lazy()'dir. Bu metod parametre olarak dinamik yüklediğimiz component'ı saran bir fonksiyon olmalıdır.

⚠️ React.lazy()'e verdiğimiz parametre promise dönmelidir.

// Geleneksel yöntem:
import LoginModal from './components/LoginModal'

// React.lazy() dinamik import yöntemi:
const LoginModal = React.lazy(() => import('./components/LoginModal'))

Ufak bir değişiklikle LoginModal component'ını dinamik olarak yüklemiş olduk. Şimdi de bu component'ı render etme kısmına geçelim.

React Suspense

Dinamik olarak yüklediğimiz component'ı render edip ve bu component yüklenene kadar geçen zamanda yedek bir içerik gösterebilmemize yarayan React'ın özel component'ı Suspense'dir. Suspense component'ı fallback adında tek bir props alır. Ve bu fallback props'u dinamik component'ımız yüklenene kadar veya component yüklenmesinde herhangi bir sorun olduğu zaman render edilecek içeriği belirttiğimiz yerdir.

import React, { Suspense } from 'react'

// Dynamic Imports
const LoginModal = React.lazy(() => import('./components/LoginModal'))

const App = () => {
  // ...
  const showLoginModal = () => {}

  return (
    <>
      {/* ... */}
      <button onClick={showLoginModal}>Giriş Yap</button>

      {isLoginModalOpen && (
        <Suspense fallback={<div>Yükleniyor...</div>}>
          <LoginModal />
        </Suspense>
      )}
      {/* ... */}
    </>
  )
}

Basitçe Suspense kullanımı bu şekilde. Ayrıca fallback props'u olarak bir component da verebiliyoruz.

<Suspense fallback={<LoadingOverlay />}>
  <LoginModal />
</Suspense>

Bu yazıyı ufak bir örnek ve bu örneğin sonuçlarını göstererek bitirmek istiyorum. Public bir api'den kullanıcıları listeleyen küçük bir react uygulaması yapacağız. Kullanıcı kişileri göster butonuna tıkladığında api'ye istek atıp dönen isteğin cevabında dinamik olarak oluşturduğumuz UserList component'ını render edeceğiz.

Yukarıdaki iframe'den bu yazı için geliştirdiğimiz minik uygulamayı görebilirsiniz. Son olarak yaptıklarımız işe yaradı mı gerçekten de code splitting yapabildik mi inceleyelim. Tarayıcımızdan developer tools'u açalım ve network tab'ına tıklayalım sayfayı yenileyelim.

Kullanıcıları göster butonuna basmadan önce:

React Suspense ve Lazy Sonuçları 1

Kullanıcıları göster butonuna bastıktan sonra:

React Suspense ve Lazy Sonuçları 2

Ekran görüntüleri de gösteriyor ki Suspense ve React.lazy() gayet güzel bir şekilde çalışıyor 🥳. UserList component'ını isteğe bağlı olarak sadece etkileşime geçileceği zaman yüklemiş olduk.

* * *

Yorumlar

Soru, cevap ve destekleriniz için aşağıdan yorum bırakmayı unutmayın.

* * *
useEffect vs useLayoutEffectReact Error Boundaries ile Hata Yönetimi