React Architecture: Bir React Uygulamasını Yapılandırma ve Organize Etme

Fatih Küçükkarakurt

Fatih Küçükkarakurt

8 min read

Yeni bir React uygulaması için mimari oluşturma konusunda fikir sahibi olabileceğiniz bu makale, bir rehber niteliğinde olacak.

Aslında bir React uygulamasını düzenlemenin doğru yolu konusunda fikir birliği yapamayız. React size bu konuda özgürlük tanır. Ancak bu özgürlükle birlikte kendi mimarinize karar verme sorumluluğu doğar. components,containers gibi klasörler oluşturmakta bu yollardan biridir. Ancak bunun biraz daha iyi bir yolu var.

Uygulamalarımı, kullanım, anlaşılabilirlik ve esneklik konularında kolay bir seviyede tutmaya çalışırım. Bunun için kullandığım React mimarisini beraber inceleyeceğiz. Önemli olduğunu düşündüğüm ana konsept, mimariyi tip yerine özellik odaklı hale getirmek, yalnızca paylaşılan bileşenleri global düzeyde organize etmek ve diğer tüm ilgili varlıkları yerelleştirilmiş görünümde modülerleştirmekten geçiyor.

Kullanılacağını Varsayacağımız Teknolojiler

Bu makale yalnızca fikir odaklı olacağından, projemiz aşağıdaki teknolojileri kullanıyor gibi düşünelim:

Styled Components, CSS modülleri veya özel bir Sass kurulumunun ideal olup olmadığına dair, sizi ikna edebileceğim bir fikrim yok. Ancak Styled Components'in stillerinizi modüler tutmak için muhtemelen en iyi seçeneklerden biri olduğunu söyleyebilirim.

Ayırca testlerin üst düzey bir tests klasöründe değil, kodun yanında varolduğunu varsayacağım.

Eğer, Redux Toolkit yerine Vanilla Redux kullanıyorsanız, Redux'unuzu her iki şekilde de feature slices olarak ayarlamanızı tavsiye ederim.

Storybook hakkında da kararsız olduğumu belirtmek isterim. Ancak projenizde kullanmayı tercih ederseniz, bu dosyalarla nasıl bir konsept olabileceğinden de bahsedeceğim.

Burada, kitap listeleme sayfası, yazar listeleme sayfası ve bir kimlik doğrulama sistemi olan, "Basit bir Kütüphane" uygulaması oluşturacağız.

Dizin Yapısı

Global dizin yapımız aşağıdaki gibi olacaktır:

  • Assets - resimler, svg dosyaları, logolar vb. global statik varlıklar.
  • Components - Form bileşenleri, butonlar vb. global yeniden kullanılabilir bileşenler.
  • Services - Javascript modülleri
  • Store - Global Redux mağazası
  • Utils - Yardımcı programlar vb.
  • Views - "Pages" olarakta adlandırabiliriz. Uygulamamızın çoğu burada bulunacak.

Bazı göz aşinalığımız olan kuralları kullanmakta fayda var. src klasörünün içinde tüm dosyalarımızı toplamak iyi bir fikir. Ayrıca tüm bunların yanına index.js ve App.js dosyalarımızı da ekleyebiliriz. index.js, uygulamamızın giriş noktasıdır. App.js ise yetkilendirme ve yönlendirme gibi konularda söz sahibi olacak.

└── /src
    ├── /assets
    ├── /components
    ├── /services
    ├── /store
    ├── /utils
    ├── /views
    ├── index.js
    └── App.js

Bu bir TypeScript projesi ise types, middleware , context gibi yapılarıda kullanabilirdiniz.

Aliases

Sistemimizi Aliases kullanacak şekilde kurmak iyi bir fikirdir. Böylece @assets, @components gibi yöntemlerle herhangi bir varlığı basitçe içeri aktarabiliriz.

module.exports = {
  resolve: {
    extensions: ['js', 'ts'],
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@assets': path.resolve(__dirname, 'src/components'),
      '@components': path.resolve(__dirname, 'src/components'),
      // ...etc
    },
  },
}

Bu yöntem, proje içindeki herhangi bir yerden içe aktarmayı çok daha kolay hale getirir ve asla ../../../../../components/ gibi bir şeyle karşılaşmazsınız.

Components

components klasöründe, ben genellikle türlere göre bir gruplandırma yolu izliyorm. forms, tables, buttons, layout vb. gibi. Tabiki de burada saydıklarımız üzerinde çalıştığınız uygulamaya göre değişecektir.

Bu makaledeki örneğimizde, ya kendi form sistemimizi oluşturduğumuzu ya da mevcut bir form sistemine kendi bağlantılarımızı oluşturduğumuzu varsayalım (örneğin, Formik ve Material UI'yi birleştirmek gibi). Bu durumda, her bileşen (TextField, Select, Radio, Dropdown, vb.) için bir klasör oluşturursunuz ve içinde, bileşenin kendisi, stiller, testler ve kullanılıyorsa Storybook için bir dosya olur.

  • Components.js - Gerçek React bileşeni
  • Component.styles.js - Bileşen için Styled Components dosyası
  • Component.test.js - Testler
  • Component.stories.js - Storybook dosyası

Bana göre bu, TÜM bileşenlerin dosyalarını içeren bir klasöre, tüm testleri içeren bir klasöre ve tüm Storybook dosyalarını vb. içeren bir klasöre sahip olmaktan çok daha mantıklı. Bu şekilde, ilgili her şey bir arada gruplandırılmış ve bulunması daha kolay oluyor.

└── /src
    └── /components
        ├── /forms
        │   ├── /TextField
        │   │   ├── TextField.js
        │   │   ├── TextField.styles.js
        │   │   ├── TextField.test.js
        │   │   └── TextField.stories.js
        │   ├── /Select
        │   │   ├── Select.js
        │   │   ├── Select.styles.js
        │   │   ├── Select.test.js
        │   │   └── Select.stories.js
        │   └── index.js
        ├── /routing
        │   └── /PrivateRoute
        │       ├── /PrivateRoute.js
        │       └── /PrivateRoute.test.js
        └── /layout
            └── /navigation
                └── /NavBar
                    ├── NavBar.js
                    ├── NavBar.styles.js
                    ├── NavBar.test.js
                    └── NavBar.stories.js

components/forms dizininde bir index.js dosyası olduğunu fark edeceksiniz. Açık olmadıkları için index.js dosyalarını kullanmaktan kaçınmak genellikle haklı olarak önerilir, ancak bu durumda mantıklıdır. En sonunda tüm formların bir dizini olur ve şöyle görünür:

//src/components/forms/index.js
import { TextField } from './TextField/TextField'
import { Select } from './Select/Select'
import { Radio } from './Radio/Radio'

export { TextField, Select, Radio }

Ardından, bir veya daha fazla bileşen kullanmanız gerektiğinde, hepsini tek seferde kolayca içe aktarabilirsiniz.

import { TextField, Select, Radio } from '@components/forms'

Bu yaklaşımı, forms'daki her klasörün içinde bir index.js oluşturmaktan daha çok tavsiye ederim. Böylece birçok index.js dosyasının aksine tüm dizini indeksleyen tek bir index.js dosyanız var.

Services

services dizini, components'den daha az önemlidir, ancak uygulamanın geri kalanının kullandığı düz bir JavaScript modülü yapıyorsanız, kullanışlı olabilir. Yaygın kullanılan bir örnek vermek gerekirse, şöyle görünebilecek bir LocalStorage modülünden bahsedebiliriz:

└── /src
    └── /services
        ├── /LocalStorage
        │   ├── LocalStorage.service.js
        │   └── LocalStorage.test.js
        └── index.js

Service'e bir örnek:

//src/services/LocalStorage/LocalStorage.service.js
export const LocalStorage = {
  get(key) {},
  set(key, value) {},
  remove(key) {},
  clear() {},
}
import { LocalStorage } from '@services'

LocalStorage.get('foo')

Store

Global veri deposu, store dizininde yer alacaktır. Her özelliğin, Redux Toolkit Slice'ın yanı sıra eylemler ve testler içeren bir klasörü olacaktır. Bu kurulum normal Redux ile de kullanılabilir, sadece bir slice yerine bir .reducers.js dosyası ve .actions.js dosyası oluşturacaksınız. Saga kullanıyorsanız, Redux Thunk eylemleri için .actions.js yerine .saga.js kullanabilirsiniz.

└── /src
    ├── /store
    │   ├── /authentication
    │   │   ├── /authentication.slice.js
    │   │   ├── /authentication.actions.js
    │   │   └── /authentication.test.js
    │   ├── /authors
    │   │   ├── /authors.slice.js
    │   │   ├── /authors.actions.js
    │   │   └── /authors.test.js
    │   └── /books
    │       ├── /books.slice.js
    │       ├── /books.actions.js
    │       └── /books.test.js
    ├── rootReducer.js
    └── index.js

Ayrıca, her yerde const [isOpen, setIsOpen] = useState(false) kullanmaktan daha iyi bir yöntem olacak şekilde, modları, toastları, kenar çubuğu geçişini ve diğer küresel ui durumlarını işlemek için store'un kullanıcı arayüzü bölümü gibi bir şey ekleyebilirsiniz.

rootReducer'da tüm slice'larınızı içe aktarır ve bunları combineReducers ile birleştirirsiniz. Ardından index.js'de store'u yapılandırırsınız.

Utils

Projenizin bir utils klasörüne ihtiyacı olup olmadığı size kalmış, ancak uygulamanın birden çok bölümünde kolayca kullanılabilen doğrulama ve dönüştürme gibi bazı genel yardımcı işlevler olduğunu düşünüyorum. Düzenli bir utils klasörü oluşturursanız (yalnızca binlerce işlevi içeren bir helpers.js dosyasına sahip olmak yerine), projenizin organizasyonuna yardımcı olabilir.

└── src
    └── /utils
        ├── /constants
        │   └── countries.constants.js
        └── /helpers
            ├── validation.helpers.js
            ├── currency.helpers.js
            └── array.helpers.js

utils klasörü, global düzeyde tutmanın mantıklı olduğunu düşündüğünüz her şeyi içerebilir. "Çok katmanlı" dosya adlarını tercih etmiyorsanız, buna validation.js diyebilirsiniz, ancak gördüğüm kadarıyla açık olmak projeden hiçbir şey eksiltmez ve IDE'nizde arama yaparken dosya adlarında gezinmeyi kolaylaştırır.

Views

Uygulamanızın ana bölümünün olacağı yer: views dizinidir. Uygulamanızdaki herhangi bir sayfa elbette bir "görünümdür". Bu küçük örnekte, görünümler Redux store ile oldukça iyi örtüşüyor, ancak store ve görünümlerin tamamen aynı olması söz konusu değil, bu yüzden ayrılar.

Bir görünüm içindeki herhangi bir şey, muhtemelen yalnızca o belirli görünümde kullanılacak bir öğedir. Yalnızca /books yolunda kullanılacak bir BookForm ve yalnızca /authors yolunda kullanılacak bir AuthorBlurb gibi.

Belirli formlar, modlar ve butonlar genel olmayacak herhangi bir bileşen içerebilir.

Tüm sayfalarınızı components/pages dizininde bir araya getirmek yerine her şeyi alan odaklı tutmanın avantajı, uygulamanın yapısına bakmayı, kaç tane ebeveyn görünüm olduğunu ve nerede olduklarını bilmeyi gerçekten kolaylaştırmasıdır.

└── /src
    └── /views
        ├── /Authors
        │   ├── /AuthorsPage
        │   │   ├── AuthorsPage.js
        │   │   └── AuthorsPage.test.js
        │   └── /AuthorBlurb
        │       ├── /AuthorBlurb.js
        │       └── /AuthorBlurb.test.js
        ├── /Books
        │   ├── /BooksPage
        │   │   ├── BooksPage.js
        │   │   └── BooksPage.test.js
        │   └── /BookForm
        │       ├── /BookForm.js
        │       └── /BookForm.test.js
        └── /Login
            ├── LoginPage
            │   ├── LoginPage.styles.js
            │   ├── LoginPage.js
            │   └── LoginPage.test.js
            └── LoginForm
                ├── LoginForm.js
                └── LoginForm.test.js

Projenizi hiç bu şekilde kurmadıysanız, her şeyi klasörler içinde tutmak can sıkıcı görünebilir. Ancak alışmak oldukça kolaydır ve yalın çalışma klasörleri oluşturmanız oldukça önemlidir.

Özetle

Bu, büyük uygulamalar için iyi ölçeklenen, test ve stil oluşturmanın yanı sıra her şeyi özellik odaklı bir şekilde bir arada tutan, React organizasyonları için önerilerimin yer aldığı bir makaledir. components ve containers içerisinde bulunan her şeyin geleneksel yapısından ziyade, Redux'un Hooks ile uygulanması çok daha kolay olduğu ve "akıllı" containers ve "aptal" components hususlarının artık gerekli olmaması nedeniyle bu sistem biraz daha eskidir.

Anlatmış olduğumuz sistem her uygulama türü için anlamlı olmayabilir. Uzun süredir dosya sistemimi bu şekilde düzenliyorum. Bu sistemin iyileştirilebileceği yollar veya hak ettiği diğer sistemler hakkında herhangi bir yorum duymak harika olurdu.

Kendinize iyi bakın. Sağlıcakla kalın.

Burası Anatoliacode.

Anatoliacode Makale Aboneliği

Bize abone olarak tüm makaleleri ilk siz okuyabilirsiniz. Ayrıca asla reklam veya spam yapmıyoruz.