React’in iç dinamikleri yazı dizimde, arka planda kullanılan algoritmalardan bahsetmeye çalışacağım. React iç dünyasında neler olup bittiğini anlayarak, daha performanslı uygulamalar geliştirebiliriz. Perfomansın yanında uygulama tutarlılığını da sağlayabilmek bizim en büyük kazançlarımızdan biri olacaktır.

Sanal DOM

Sanal DOM bir programlama kavramıdır. UI’ın sanal görüntüsüdür. birleştirme-eşleştirme denilen bir süreç ile gerçek DOM’la senkronize edilir. ReactDOM bu senkronizasyonu sağlayan bir kütüphanedir.
React bu yaklaşımla tanımlamalı bir API olur. Siz React’a arayüzün hangi durumda olması gerektiğini söylersiniz, react’da DOM’u o durum ile eşleştirir. Bu sizi özellik manupilasyonundan, olay yakalamadan ve manuel DOM güncellemesinden soyutlar.
Sanal DOM dil bağımsız tasarım kalıbıdır. React’de ise sanal DOM kullanıcı arayüzünü temsil eden React elemanları ile ilişkilendirilir. Ayrıca React iç objelerinden olan “fiber” ler sanal DOM’un bir parçası olarak anılırlar.

Birleştirme-Eşleştirme

React güncellemeleri düşünmemeniz için tanımlamalı bir API sunar. Bu uygulamaların kolay yazılmasını sağlar, fakat iç dinamiklerden habersiz olabiliriz. Bu yazımızda genel olarak React’in fark algoritmasından bahsedeceğiz. Bu algoritmayı bilirsek arayüz güncellemelerini kontrol edebilir ve yüksek performanslı uygulamalar yazabiliriz.

Motivasyonumuz

React render() fonksiyonu ile eleman ağacı oluşturur. Durum ve özellikler güncellemelerinde, render() farklı bir ağaç döner. React güncellemeleri yansıtırken bunu en efektif nasıl yapacağını hesaplar.
Geleneksel algoritmalar bir eleman ağacını diğer eleman ağacına O(n3) maliyeti ile çevirir (n ağaçtaki eleman sayısıdır). Ağaçta 1000 eleman olduğunu düşünelim, 1 milyon karşılaştırma demektir.
React bunun yerine daha efektif O(n) maliyetinde bir algoritma kullanır. Algoritma 2 varsayıma dayanır:
1- Farklı tipteki iki eleman farklı ağaç üretir
2- Geliştirici elemanlara “key” özelliği vererek farklı renderlarda elemanların değişmeden kalmasını sağlar. Buna elemanı tanıtmak denir.
Pratikte çoğu durumda bu iki varsayım geçerli olur.

Fark Algoritması

React iki ağacın farkına bakarken önce kök elemanları karşılaştırır. Sonraki tavrı kök elemanların tipine göre değişir.

Farklı tipte DOM elemanları

Kök elemanları farklı olduğunda React eski ağacın yerine yenisini baştan oluşturur.
Eski ağacı yok ederken, tüm DOM düğümleri yok edilir. Bileşen örneklerinde componentWillUnmount() olayı çalışır. Yeni ağaç oluşurken DOM düğümleri DOM’a eklenir. Bileşen örneklerinde sırasıyla componentWillMount() ve componentDidMount() olayları çalışır. Eski ağaç ile ilişkili durum verisi tamamen kaybolur.
Kök altındaki bütün bileşenler kaldırılır ve durum verileri kaybolur. Mesela aşağıdaki OySayisi bileşeni yok edilip yerine yenisi monte edilir.

<div> <OySayisi/> </div> <span> <OySayisi/> </span>

Aynı tipte DOM elemanları

React aynı tipteki DOM elemanları karşılaştırırken, attribute’lerine bakar ve sadece değişen attribute’leri günceller. Örneğin:

<div className=”oncesi” title=”resim” /><div className=”sonrasi” title=”resim” />

React bu elemanları karşılaştırırken ilgili DOM düğümünde sadece className ‘i değiştireceğini bilir.
style elemanını güncellerken React sadece değişen özelliklerin güncelleneceğini bilir. Örneğin:

<div style={{color: ‘red’, fontWeight: ‘bold’}} /><div style={{color: ‘green’, fontWeight: ‘bold’}} />

Bu elemanları birbirine çevirirken ReactfontWeight değil sadece color stilini değiştirir.
DOM düğümünü işledikten sonra çocuklarını özyinelemeli olarak işler.

Aynı tipte Bileşenler

Bileşen güncellendiğinde, değişiklik esnasında durum verisi korunur. React yeni eleman oluşturmak için ilgili bileşen örneğinin prop’larını günceller; sonrasında bileşen örneğinin componentWillReceiveProps() ve componentWillUpdate() metodları çağırır.
Sonrası da render() metodu çağırılır ve fark algoritması önceki sonuç ve yeni sonuç arasında dolaşır ve karşılaştırır.

Çocuklar’ı recurse etmek

Varsayılan olarak React bir DOM düğümünün önceki ve değiştirilmiş çocuklarını aynı anda gezer, eğer değişiklik isteniyorsa değiştirir.
Örneğin çocukların sonuna yeni bir eleman eklemek çok rahat çalışır:

<ul><li>birinci</li><li>ikinci</li></ul><ul><li>birinci</li><li>ikinci</li><li>üçüncü</li></ul>

React önceki ve değiştirilecek versiyonda <li>birinci</li> ve <li>ikinci</li> elemanlarını karşılaştırır ve sadece <li>üçüncü</li> elemanını ağaca ekler.
Eğer basit mantıkla ağacı oluşturduğumuzu düşünün. elemanı en başa eklemek en kötü performansla nihai ağaç oluşturulur:

<ul><li>Duke</li><li>Villanova</li></ul><ul><li>Connecticut</li><li>Duke</li><li>Villanova</li></ul>

React bu örnekte:

<li>Duke</li> and <li>Villanova</li>

elemanlarını aynı bırakmak yerine ağacı oluştururken tüm elemanları değiştirir. Bu etkin olmayan çalışma şekli problem olabilir.

Anahtar atama yöntemi

Bu verimsizliği çözmek için React “key” özelliğini destekler. Çocuk elemanların anahtarları varsa, React önceki ve değiştirilecek ağaçta elemanları anahtar ile eşleştirir. Örneğin verimsiz olan yukarıdaki örnekteki elemanlara anahtar eklediğimizde, dönüşümü etkin hale getirmiş oluruz:

<ul><li key=”2015">Duke</li><li key=”2016">Villanova</li></ul><ul><li key=”2014">Connecticut</li><li key=”2015">Duke</li><li key=”2016">Villanova</li></ul>

Şimdi React ‘2014’ anahtarlı elemanın yeni olduğunu, ve ‘2015’ ve ‘2016’ anahtarlı elemanların sadece yer değiştirdiğini bilir.
Pratikte, anahtar atamaları zor değildir, veriden dinamik olarak atanabilir.

<li key={item.id}>{item.name}</li>

Anahtar, modelinizdeki ID özelliğinden veya belli alanlar hashlenerek oluşturulur. Anahtar sadece elemanın kardeşleri arasında tekil olması yeterlidir. Tüm ağaçta global olmasına gerek yoktur.
Son opsiyon, elemanın indeksini anahtar olarak atamak olabilir. Bu yöntem elemanların sırası değişmiyorsa iyi çalışır, fakat tekrar sıralamalar yavaş olacaktır.
Bileşen örneklerinde indeks anahtar olarak atandığında bileşenin durum verisi üzerinde farklı etkiler oluşturabilir. Bileşen indeksi değişirse bileşene gönderilen kontrolsüz girdiler beklenmeyen değişiklikler yapabilir.

Performansa gelirsek

React’in güvendiği varsayımlar gerçekleşmezse performansı can çekişir:

  1. Farklı tipteki bileşenleri React eşleştirmekle uğraşmaz, aynı çıktıyı verecekseniz bileşenleri aynı tip yapın. Genelde pratikte bu tip durumlarda farklı tip kullanılmaz.
  2. Anahtarlar tutarlı, öngörülebilir ve tekil olmalı. Math.Random() gibi tutarsız anahtarlar birçok bileşen örneklerinin ve DOM düğümlerinin tekrar yaratılmasına yol açacak, bu da performans düşüklüğü ve çocuk bileşenlerin durum verisinin kaybolmasına yol açacaktır.