Skip to content

Latest commit

 

History

History
230 lines (143 loc) · 14.1 KB

File metadata and controls

230 lines (143 loc) · 14.1 KB

Bu makale patterns.dev adresinde yayınlanan HOC Pattern makalesinin Türkçe çevirisidir.

Higher Order Components (HOC) Pattern

Yeniden kullanılabilir mantığı, uygulamanızın her yerinde props olarak component'lere aktarın.

Uygulamamız içerisinde, aynı mantığı farklı component'lerde sık sık kullanmak isteriz. Bu mantık, bir component'e belirli bir stil vermek, yetkilendirme sağlamak veya global bir state eklemek olabilir.

Aynı mantığı birden fazla component'te kullanabilmenin bir yolu, Higher Order Component modelini kullanmaktır. Bu model, component mantığını uygulamamızın her yerinde yeniden kullanabilmemizi sağlar.

Higher Order Component (HOC) başka bir component'i parametre olarak alan component'e denir. HOC, parametre olarak aldığı component'e uygulamak istediğimiz mantığı içerir. Bu mantık component'e uygulandıktan sonra; HOC, component'i eklenmiş mantıkla birlikte döndürür.

Diyelim ki, uygulamamızda birden fazla component'e aynı stili uygulamak istiyoruz. Her seferinde local stiller oluşturmak yerine, kendisine aktarılan component'lere belirli bir stil uygulayan bir HOC yaratabiliriz.

function withStyles(Component) {
  return props => {
    const style = { padding: '0.2rem', margin: '1rem' }
    return <Component style={style} {...props} />
  }
}

const Button = () = <button>Click me!</button>
const Text = () => <p>Hello World!</p>

const StyledButton = withStyles(Button)
const StyledText = withStyles(Text)

Yukarıda, Button ve Text component'lerinin özelleştirilmiş versiyonları olan StyledButton ve StyledText component'lerini oluşturduk. Artık iki component de withStyles HOC'si aracılığıyla eklenmiş stilleri içeriyor.

Şimdi de, daha önce Container/Presentational Pattern içerisinde kullanılmış olan DogImages örneğini inceleyelim. Bu uygulama, API üzerinden gelen köpek fotoğraflarını listeliyor.

Örneğin kodlarına CodeSandbox üzerinden erişebilirsiniz.

Şimdi kullancı deneyimini biraz geliştirelim. API üzerinden veri gelirken, kullanıcıya bir Loading... ekranı göstermek istiyoruz. Bu veriyi DogImages component'ine direkt olarak eklemek yerine, bu mantığı bizim için ekleyecek bir HOC kullanabiliriz.

withLoader adında bir HOC yaratalım. Bir HOC bir component almalı ve o component'i döndürmelidir. Bu durumda; withLoader, veri yüklenene kadar Loading... yazısını gösterecek bir element almalıdır.

Kullanmak istediğimiz withLoader higher order component'inin yalın bir halini yaratalım.

function withLoader(Element) {
  return (props) => <Element />;
}

Ancak, yalnızca alınan elementi döndürmek istemiyoruz. Bunun yerine, alınacak elementin istenen verinin hala yüklenip yüklenmediğini söyleyen kontrolü içermesini istiyoruz.

withLoader higher order component'ini yeniden kullanılabilir yapabilmek için, Dog API URL'ini hardcoded şekilde yerleştirmeyeceğiz. Bunun yerine, *API URL'*ini withLoader component'ine argüman olarak geçireceğiz ki bu component, herhangi bir API adresinden veri çekerken Loading... göstergesine ihtiyaç duyan başka bir component tarafından kullanılabilsin.

function withLoader(Element, url) {
  return (props) => {};
}

Higher order component'ler, veri hala yükleniyorken, Loading... yazısını göstermemizi sağlayan mantığı eklemek istediğimiz, yukarıdaki örnekte props => {} şeklinde bir functional component olan, bir element döndürür. Veri yüklendiğinde ise, component alınan verileri props olarak higher order component'e geçirmelidir.

Örneğin kodlarına CodeSandbox üzerinden erişebilirsiniz.

Mükemmel! Herhangi bir component ve herhangi bir URL'i alabilen bir HOC yarattık.

useEffect hook'unda, withLoader higher order component'i veriyi URL'ini parametre olarak verdiğimiz API adresinden çeker. HOC, veri henüz döndürülmemişken Loading... yazısını içeren bir element döndürür. Veri alındığında, bu veriyi data state'inde tutarız. Artık data state'i null olmadığından, HOC'ye verilmiş olan component'i render edebiliriz. Peki, DogImages listesinde Loading... göstergesinin görünmesini sağlayacak davranışı uygulamamıza nasıl ekleriz?

Artık DogImages.js dosyasında, DogImages component'ini olduğu gibi export etmek istemiyoruz. Bunun yerine, props yoluyla DogImages component'ini almış olan withLoading higher order component'ini export etmek istiyoruz.

export default withLoading(DogImages);

withLoader higher order component'i, veriyi hangi adresten çekeceğini bilmek için aynı zamanda URL de bekliyor.

export default withLoader(
  DogImages,
  "https://dog.ceo/api/breed/labrador/images/random/6"
);

withLoader component'i döndüreceği elementi, bu durumda DogImages component'ini, bir data prop'u ile birlikte döndürdüğünden bu data prop'una DogImages component'i içinden erişebiliyoruz.

Örneğin kodlarına CodeSandbox üzerinden erişebilirsiniz.

Mükemmel! Artık veri yüklenirken Loading... yazısını görebiliyoruz.

Higher Order Component modeli, kullanacağımız mantığı tek yerde tutarken aynı mantığı birden fazla component'e aktarabilmemize olanak sağlıyor. withLoader component'i kendisine verilen component veya URL ile ilgilenmez. Kısaca, geçerli bir component ve geçerli API adresi olduğu sürece API'dan gelen veriyi component'e aktarır.


Composing

Birden fazla higher order component de oluşturabiliriz. Diyelim ki, kullanıcı DogImages listesinin üzerine geldiğinde Hovering! yazısını göstermek istiyoruz.

Bunun için, kendisine verdiğimiz elemente bir hovering prop'u sağlayacak bir HOC yaratmamız gerekiyor. Bu prop'u kullanarak, kullanıcının DogImages listesinin üzerine gelip gelmediğine bağlı olarak koşullu olarak yazıyı render edebiliriz.

Şimdi de withLoader higher order component'ini withHover higher order component'ine aktarabiliriz.

Örneğin kodlarına CodeSandbox üzerinden erişebilirsiniz.

Artık, DogImages elementi withHover ve withLoader tarafından sağlanan tüm prop'ları içeriyor. Şimdi hovering prop'unun true ya da false olması koşuluna bağlı olarak Hovering! yazısını render edebiliriz.

"recompose, HOC oluşturma konusunda iyi bilinen bir kütüphanedir. Ancak React Hook'ları çoğunlukla HOC'lerin yerini aldığından recompose kütüphanesi artık geliştirilmiyor. Bu sebeple de makale de yer verilmeyecek."


Hooks

Bazı durumlarda, HOC modeli yerine React Hook'larını kullanabiliriz.

withHover higher order component'inin yerine useHover hook'unu kullanalım. Bir HOC oluşturmak yerine, parametre olarak verdiğimiz elemente mouseOver ve mouseLeave event listener'larını ekleyen bir hook export ediyoruz. Artık, elementi HOC ile kullandığımız gibi kullanamayız. Onun yerine, mouseOver ve mouseLeave event'lerini kontrol etmesi gereken hook'tan bir ref döndüreceğiz.

Örneğin kodlarına CodeSandbox üzerinden erişebilirsiniz.

useEffect hook'u component'e bir event listener ekler ve kullanıcının o anda elementi hover edip etmemesine bağlı olarak hovering state'inin değerini true ya da false olarak değiştirir. Hem ref hem de hovering değerleri hook'tan döndürülmelidir: ref değeri mouseOver ve mouseLeave event'lerini alacak olan component'e bir referans eklemek için, hovering ise koşullu olarak Hovering! yazısını render etmek için döndürülür.

DogImages component'ini withHover higher order component'ine vermek yerine, artık useHover hook'unu direkt olarak DogImages component'inin içinde kullanabiliriz.

Örneğin kodlarına CodeSandbox üzerinden erişebilirsiniz.


Genel olarak konuşursak, React Hooks HOC modelinin yerini alamaz.

"Çoğu durumda, React Hook'ları yeterli olacaktır ve katmanlı yapıyı azaltmanıza yardımcı olabilir." -React Docs

React'ın dokümantasyonuna göre, hook'ları kullanmak component ağacının derinliğini azaltabilir. HOC modelini kullanırsak da, çok katmanlı bir component ağacıyla karşı karşıya kalmak kolaydır.

<withAuth>
  <withLayout>
    <withLogging>
      <Component />
    </withLogging>
  </withLayout>
</withAuth>

Component'e direkt olarak bir hook ekleyerek component'leri iç içe kullanmak zorunda kalmayız.

Higher Order Component'leri kullanmak, aynı mantığı tek bir yerde tutup kontrol ederken birden fazla component içinde kullanmayı mümkün kılar. Hook'lar ise component'e özel bir davranış ekleyebilmemizi sağlar, fakat HOC modeliyle karşılaştırıldığında eğer birden fazla component bu davranışa bağlıysa hatayla karşılaşma ihtimalini de artırmış olur.

HOC'ler için uygun durumlar:

  • Değiştirilmemiş, aynı davranışın uygulama boyunca birdan fazla component tarafından kullanılması gerekiyorsa,
  • Component eklenmiş mantık olmadan, tek başına da çalışabiliyorsa,

Hook'lar için uygun durumlar:

  • Davranışın, onu kullanan her component tarafından özelleştirilmesi gerekiyorsa,
  • Davranış uygulamanın her yerinde değil de, bir veya birkaç component tarafından kullanılıyorsa,
  • Davranış component'e çok sayıda prop ekliyorsa,

Kullanım Senaryosu

HOC modelini kullanan bazı kütüphaneler hook'ların çıkışından sonra hook desteği eklediler. Bunun güzel bir örneği Apollo Client.

"Bu örneği anlamak için Apollo Client ile ilgili bir tecrübeye ihtiyaç yoktur."

Apollo Client'ı kullanmanın bir yolu graphql() higher order component'ini kullanmaktır.

Örneğin kodlarına CodeSandbox üzerinden erişebilirsiniz.

graphql() higher order component'iyle, çekilen veriyi bu higher order component'ine geçirilerek kullanılan component'lerde erişilebilir kılarız. Hala, graphql() higher order component'ini direkt olarak kullanabiliyor olsak da bazı dezavantajları var.

Bir component birden fazla resolver'e ihtiyaç duyduğunda birden fazla graphql() higher order component'i compose etmek gerekiyor. Birden fazla HOC compose etmek verinin component'e nasıl aktarıldığının anlamayı zorlaştırıyor. Ayrıca bazı durumlarda HOC'lerin sırasının da önemli olmasıyla birlikte kodu refactor ederken kolayca hataya sebep olabiliyor.

Hook'ların çıkışından sonra; Apollo, Apollo Client kütüphanesine Hooks desteği ekledi. graphql() higher order component'ini kullanmak yerine, geliştiricler kütüphanenin sağladığı hook'larla artık veriye direkt olarak erişebiliyorlar.

graphql() higher order component'inin örneğinde gördüğümüz verinin aynısını kullanan bir başka örneği inceleyelim. Bu sefer, component'lere veriyi Apollo Client kütüphanesinin bize sağladığı useMutation hook'u ile sağlayacağız.

Örneğin kodlarına CodeSandbox üzerinden erişebilirsiniz.

useMutation hook'unu kullanarak, veriyi sağlamak için yazmamız gereken kod miktarını azaltmış olduk.

Kod miktarındaki azalmanın yanı sıra, bir component'te birden fazla resolver kullanmak da oldukça kolay. Birden fazla HOC compose etmektense, bir component içinde birden fazla hook kullanabiliriz. Verinin component'e nasıl geldiğini anlamak bu şekilde çok daha kolaydır. Ayrıca component'leri refactor ederken ya da daha küçük parçalara ayırırken de geliştirici deneyimini artırmış olur.


Artıları

HOC modelini kullanmak, yeniden kullanılabilir olmasını istediğimiz mantığın tek bir yerde kalmasını sağlar. bu da aynı kodu uygulama bayınca farklı yerlerde kullanıp hata yayma olasılığımızı azaltır. Tüm mantığı tek bir yerde tutarak, yazdığımız kodu DRY prensibine uygun yazmış ve seperation of concerns konseptini de zorunlu olarak uygulamış oluruz.


Eksileri

HOC'nin elemente geçirdiği prop'un ismi bir isimlendirme çakışmasına sebep olabilir.

function withStyles(Component) {
  return props => {
    const style = { padding: '0.2rem', margin: '1rem' }
    return <Component style={style} {...props} />
  }
}

const Button = () = <button style={{ color: 'red' }}>Click me!</button>
const StyledButton = withStyles(Button)

Bu durumda, withStyles higher order component'i ona verilen elemana style adında bir prop geçiriyor. Ama Button component'inin zaten style adında bir prop'u var. Prop'ların ismini değiştirerek ya da onları birleştirerek, HOC'nin isim çakışması durumunda düzgün çalışacağından emin olun.

function withStyles(Component) {
  return props => {
    const style = {
      padding: '0.2rem',
      margin: '1rem',
      ...props.style
    }

    return <Component style={style} {...props} />
  }
}

const Button = () = <button style={{ color: 'red' }}>Click me!</button>
const StyledButton = withStyles(Button)

Ona verilen component'e prop'ları aktaran birden fazla compose edilmiş HOC kullanırken, hangi HOC'nin hangi prop'tan sorumlu olduğunu bilmek zor olabilir. Bu da, uygulamanın debug edilmesini ve ölçeklenmesini engelleyebilir.


Kaynaklar