Bir üst-seviye bileşen (İngilizcesi Higher-Order Component - HOC) React’te bileşen mantığının tekrar kullanılmasına yarayan ileri düzey bir tekniktir. HOC’lar React API’nın bir parçası değildir aslında. React’ın bileşen tümleyici doğasından doğan bir modeldir.
Daha açıklayıcı olmak gerekirse, bir üst-seviye bileşen; parametre olarak bir bileşen alıp output olarak yeni bir bileşen döndüren bir fonksiyondur.
Bir bileşen; proplarını kullanıcı arayüzüne çevirirken, üst-seviye bir bileşen başka bir bileşeni alıp farklı bir bileşene çevirir.
HOC’lar Redux’un connect ve Relay’in createFragmentContainer gibi üçüncü taraf React kütüphanelerinde yaygındır.
Bu dokümanda neden üst-seviye bileşenlerin kullanışlı olduğunu tartışıp, bunları nasıl yazabileceğiniz hakkında konuşacağız.
HOC’ları Uygulama Genelindeki Sorunlar için Kullanın
Daha önce uygulama genelindeki sorunlar için mixin’lerin kullanılmasını önermiştik. Fakat o zamandan beri fark ettik ki, mixin’ler yarardan çok zarara yol açıyor. Mixin’lerden neden uzaklaştığımız konusunda ve nasıl varolan bileşenlerinizi mixin’lerden geçirebileceğiniz hakkında daha fazla bilgiye buradan ulaşabilirsiniz.
Bileşenler React’te yeniden kod kullanımının temel birimidir. Fakat, bazı davranışların alışılageldik bileşenlerle kullanılmaya uygun olmadığını göreceksiniz.
Örneğin, CommentList diye bir dış data kaynağına bağlanan bir bileşeniniz olduğunu varsayalım.
Sonra, benzer bir davranış sergileyen, tek bir blog gönderisine bağlanan bir bileşen yazıyorsunuz.
CommentList ve BlogPost birebir aynı değil - DataSource‘da farklı methodlar çağırıyorlar ve farklı çıktılar renderlıyorlar. Fakat, kodlamasının çoğu aynı:
Yükleme (mount) sırasında DataSource‘a bir değişken dinleyici eklemek.
Dinleyicinin içinde, data kaynağı değiştiğinde setState‘i çağırmak
Unmount’da, değişken dinleyiciyi kaldırmak.
Büyük bir uygulamada, bu DataSource‘a bağlanan ve setState‘i çağıran davranışın sürekli tekrarlanacağını hayal edebilirsiniz. Fakat bizim istediğimiz şey bu davranışı somutlaştırmak ve bu davranışı tek bir yerde birden fazla bileşen arasında paylaşmak. İşte bu gibi durumlarda Üst-seviye bileşenler işimize yarıyor.
CommentList ve BlogPost gibi DataSource‘a bağlanacak bileşenleri üretecek bir fonksiyon yazabiliriz. Foknsiyon arguman olarak, elde edilen datayı prop olarak alan bir alt eleman alacak. Fonksiyonun adını withSubscription koyalım.
İlk parametre kaplanan bileşen. İkinci parametre is DataSource ve anlık propları aldığı zaman ilgilendiğimiz datayı çekiyor.
CommentListWithSubscription ve BlogPostWithSubscription render edildiğinde CommentList ve BlogPost‘a bir data propu gelecek ve bu data’da DataSource‘tan çekilen en gücel data bulunacak.
Bir HOC’un input bileşenini değiştirmediğini ve davranışı kopyalamak için inheritance kullanmadığına dikkat ediniz. Bunun yerine; HOC, orjinal bileşeni bir container içine alarak bu bileşeni yaratır. Bir HOC yan etkisi olmayan saf bir fonksiyondur.
İşte bu kadar! Kapsanan bileşen, kapsayan bileşenin bütün proplarını alır ayrıca output’unu render etmek için yeni bir prop olan data‘yı alır. Data’nın neden veya nasıl kullanıldığı HOC’u ilgilendirmez ve kapsanan bileşen de data’nın nereden geldiğiyle ilgilenmez.
withSubscription normal bir fonksiyon olduğundan, istediğiniz kadar arguman ekleyebilirsiniz. Örneğin, data prop ismini değiştirilebilir yapmak isteyebilirsiniz, bu sayede HOC ve kapsanan bileşen birbirinden daha ayrık bir hale gelecektir. Ya da shouldComponentUpdate‘i veya data kaynağını ayarlayan bir arguman alabilirsiniz. Bunların mümkün olmasının sebebi ise HOC’un bileşenin nasıl tanımlandığı üzerinde tam kontrole sahip olmasıdır.
Bileşenlerde olduğu gibi, withSubscription ile kapsanan bileşen arasındaki bağlantı tamamen proplar üzerindendir. Bu da kapsanan bileşene aynı propları sağladıkları sürece bir HOC’u başka bir HOC’la değiştirmeyi kolaylaştırır. Eğer data almanıza yarayan kütüphaneleri kullanırsanız bu değişkenlik işinize yarayabilir.
Orijinal Bileşeni Değiştirmeyin. Composition Kullanın.
HOC içerisinde bileşenin prototipini değiştirmemeye çalışın.
Bununla alakalı bir kaç problem var. Birincisi, girdi olarak kullanılan bileşen geliştirilmiş bileşenden ayrı olarak yeniden kullanılamaz. Daha önemlisi, EnchancedComponent‘e başka bir HOC uygularsanız, o da componentWillRecieveProps‘u değiştirecektir; ilk HOC’un fonksiyonalitesi kaybolacaktır. Ayrıca bu HOC, yaşam döngüsü methodları içermeyen fonksiyonel bileşenlerle çalışmayacaktır.
HOC’ları değiştirmek sıkıntılı bir soyutlama yöntemidir— kodu kullanacak kişinin bunların nasıl kodlandığını bilmesi gerekiyor, yoksa diğer HOC’larla sıkıntı yaşayabilir.
HOC’lar, datayi degistirmek yerine girdi bileşenini bir kapsayıcı bileşene sararak bileşim (composition) yontemini kullanmalıdır.
Bu HOC degistiren versiyonla aynı islevsellige sahiptir ve bunu yaparken de potansiyel sıkıntılardan da kacinmaktadir. Aynı zamanda hem class bileşenlerle hem fonksiyonel bileşenlerle aynı şekilde iyi çalışıyor. Ayrıca saf bir fonksiyon olduğu için kendisi de dahil diğer HOC’larla çalışabilir durumda.
HOC’ların ve kapsayıcı bileşenler (container components) adlı bir teknik arasında bir kaç benzerlik fark etmiş olabilirsiniz. Kapsayıcı bileşenler üst ve alt seviyeyle alakalı sorumluluğu birbirinden ayrımaya yarayan bir stratejidir. Container’lar olayları dinlemek, state ve propların bileşenler arasında yollanması gibi UI’yla alakalı olaylarla ilgilenirler. HOC’lar ise container’ları kendilerini hayata geçirmekte kullanırlar. HOC’ları, parametrize edilmiş bileşen tanımları gibi düşünebilirsiniz.
Kural: Alakasız Propları Kapsanan Bileşen Üzerinden Geçirin
HOC’lar bileşenlere yeni özellikler eklerler. Ama genel olarak yaptığı işleri çok fazla değiştirmemeleri gerekir. HOC’tan dönen bir bileşenin, kapsanan bileşenle benzer bir interface’e sahip olması beklenir.
HOC’lar kendileriyle alakası olmayan propları da geçirmelidirler. Çoğu HOC şu tarz bir render metoduna sahiptir:
Bu kural HOC’ların yeterince değişken ve yeniden kullanılabilir olmasını sağlar.
Kural: Composability’yi En Üst Seviyeye Çıkartmak
Tüm HOC’lar aynı gözükmez. Bazen sadece bir argüman aldıkları da olur, bu da kapsanan bileşendir:
Genellike HOC’lar başka argüman da alırlar. Relay’den alınan bu örnekte, bir config objesi bileşenin data bağımlılıklarını tanımlamak için kullanılıyor.
HOC’ların en yaygın kullanım şekli şuna benzer:
Nasıl?! Eğer ayrıştırırsanız görmesi daha kolay olur.
Diğer bir deyişle, connect üst-seviye bileşen döndüren bir üst-seviye fonksiyondur!
Bu şekil karmaşık ve gereksiz gözükebilir, ama işe yarayan bir özelliği var. connect tarafından döndürülen HOC’lar şöyle bir kullanıma sahiptir Component => Component. Girdisi ve çıktısı aynı olan fonksiyonların birbirleriyle kullanımı çok kolaydır.
compose fonksiyonu ana bir özelliği olmasa da kullanışlı olması açısından bir çok 3. Parti kütüphaneleri tarafından kullanılır, bunların içinde lodash(lodash.flowRight olarak), Redux ve Ramda da bulunur.
Kural: Kolay Debug Etmek için Gösterilen Adı Kapsayın
HOC’lar tarafından yaratılan kapsayan bileşenler, diğer bileşenler gibi React Developer Tools tarafından gösterilir. Debug’lamayı kolaylaştırmak için, gösterilecek adı bu bileşenin bir HOC sonucu olduğunu belirtmesine özen gösterin.
En yaygın teknik, kapsanan bileşenin gösterilen adını kapsamaktır. Yani eğer üst-seviye bileşeninizin adı withSubscription ise ve kapsanan bileşenin gösterilen adı CommentList ise, gösterilen ad olarak withSubscription(CommentList)’i kullanın:
Üst-seviye bileşenler, dikkat etmeniz gereken ve eğer React’a yeniyseniz hemen göremeyeceğiniz bazı sıkıntılara sahiptir.
Render Metodu İçerisinde HOC’ları Kullanmayın
React’ın fark algılama algoritması (reconcilliation olarak adlandırılır) var olan bileşen ağacını güncellemesi veya tamamen baştan yaratması gerektiğini anlamak için bileşen kimliğini kullanır. Eğer render‘dan dönen bileşen bir önceki renderla aynıysa(===), React recursive bir şekilde bileşen ağacını yeni olanla farkını ölçelerek günceller. Eğer aynı değillerse, önceki bileşen ağacı tamamen kaldırlır.
Normalde, bunun hakkında düşünmeniz gerekmez. Fakat HOC kullanırken bu render metodu içerisinde HOC kullanayamacağınız anlamına gelir:
Buradaki tek sıkıntı performans değil — bir bileşeni tekrar mount etmek bileşenin state’inin kaybolmasına ve tüm alt-elemanlarının kaybolmasına yol açar.
Bunun yerine HOC’u bileşen tanımının dışında yapın, bu sayede sonuç bileşen sadece bir kez yaratılmış olsun. Bu sayede bu bileşenin kimliği renderlar arasında tutarlı olacaktır. Zaten genelde istenilen davranış bu şekildedir.
HOC’u dinamik olarak uygulamanız gereken durumlarda ise bunu bileşenin yaşam döngüsü metodlarında veya bileşenin constructor’ında yapabilirsiniz.
Statik Metodlar Kopyalanmalıdır
Bazen bir react bileşeninde statik bir metod tanımlamak kullanışlı olabilir. Örneğin, Relay container’ları GrahpQL ile birlikte kullanılması için getFragment diye statik bir metod açığa çıkarır.
Bir bileşene HOC uyguladığınız zaman, orijinali bir container bileşen tarafından kapsanmış olabilir. Bu da yeni bileşenin orijinal bileşenin statik fonksiyonlarından hiçbirine sahip olmadığı anlamına gelir.
Bunu çözmek için, bileşeni döndürmeden önce bütün metodları container’a kopyalayabilirsiniz.
Fakat bunun için hangi metodları kopyalamanız gerektiğini bilmelisiniz. Tüm React-dışı statik metodlarınıhoist-non-react-statics’i kullanarak kopyalayabilirisiniz:
Başka bir olası çözüm de statik metodları bileşenden ayrı olarak dışa aktarmaktır.
Üst-seviye bileşenlerin rahatlığı tüm propların kapsanan bileşene geçirilmesi olmasına rağmen, bu ref’lerde işe yaramaz. Çünkü ref’ler aslında bir prop değildir — key gibi, React tarafından özel olarak yönetilir. Eğer sahip olduğu bir bileşeni, bir HOC sonucu olaran bir elemana ref eklerseniz; bu ref en dıştaki container bileşenine denk gelir, kapsanan bileşene değil.
Bu sorunun çözümü ise React.forwardRef API’nın (React 16.3’le tanıtıldı) kullanılmasıdır. Ref’leri taşımak kısmında bu konu hakkında daha çok şey öğrenin..