Vanilla JS'de React ve JSX'i Öykünmek

Yayınlanan: 2022-03-11

Çok az insan çerçevelerden hoşlanmaz, ancak onlardan biri olsanız bile, hayatı biraz daha kolaylaştıran özellikleri not almalı ve benimsemelisiniz.

Geçmişte çerçeve kullanımına karşıydım. Ancak son zamanlarda bazı projelerimde React ve Angular ile çalışma deneyimim oldu. İlk birkaç kez kod düzenleyicimi açıp Angular'da kod yazmaya başladığımda garip ve doğal değildi; özellikle on yıldan fazla herhangi bir çerçeve kullanmadan kodlamadan sonra. Bir süre sonra bu teknolojileri öğrenmeye karar verdim. Çok hızlı bir şekilde büyük bir fark ortaya çıktı: DOM'yi değiştirmek çok kolaydı, gerektiğinde düğümlerin sırasını ayarlamak çok kolaydı ve bir kullanıcı arayüzü oluşturmak için sayfalarca kod gerekmedi.

Hala bir çerçeveye veya mimariye bağlı olmama özgürlüğünü tercih etsem de, DOM öğelerini bir tanesinde oluşturmanın çok daha uygun olduğu gerçeğini göz ardı edemezdim. Bu yüzden Vanilla JS'deki deneyimi taklit etmenin yollarını aramaya başladım. Amacım, bu fikirlerin bazılarını React'ten çıkarmak ve geliştiricilerin hayatlarını biraz daha kolaylaştırmak için aynı ilkelerin düz JavaScript'te (genellikle vanilya JS olarak adlandırılır) nasıl uygulanabileceğini göstermek. Bunu başarmak için GitHub projelerine göz atmak için basit bir uygulama oluşturalım.

Basit GitHub Arama uygulaması

Oluşturduğumuz uygulama.

JavaScript kullanarak bir ön uç oluşturmak için hangi yolla olursak olalım, DOM'a erişecek ve onu değiştireceğiz. Uygulamamız için, her bir havuzun (küçük resim, ad ve açıklama) bir temsilini oluşturmamız ve onu bir liste öğesi olarak DOM'ye eklememiz gerekecek. Sonuçlarımızı almak için GitHub Arama API'sını kullanacağız. Ve JavaScript'ten bahsettiğimize göre, JavaScript depolarını arayalım. API'yi sorguladığımızda aşağıdaki JSON yanıtını alıyoruz:

 { "total_count": 398819, "incomplete_results": false, "items": [ { "id": 28457823, "name": "freeCodeCamp", "full_name": "freeCodeCamp/freeCodeCamp", "owner": { "login": "freeCodeCamp", "id": 9892522, "avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4", "gravatar_id": "", "url": "https://api.github.com/users/freeCodeCamp", "site_admin": false }, "private": false, "html_url": "https://github.com/freeCodeCamp/freeCodeCamp", "description": "The https://freeCodeCamp.org open source codebase "+ "and curriculum. Learn to code and help nonprofits.", // more omitted information }, //... ] }

React'in yaklaşımı

React, HTML öğelerini sayfaya yazmayı çok kolaylaştırır ve bileşenleri saf JavaScript'te yazarken her zaman sahip olmak istediğim özelliklerden biridir. React, normal HTML'ye çok benzeyen JSX'i kullanır.

Ancak, tarayıcının okuduğu şey bu değil.

Kaputun altında, React, JSX'i bir React.createElement işlevine yapılan bir grup çağrıya dönüştürür. GitHub API'sinden bir öğe kullanan bir JSX örneğine bakalım ve bunun ne anlama geldiğini görelim.

 <div className="repository"> <div>{item.name}</div> <p>{item.description}</p> <img src={item.owner.avatar_url} /> </div>;
 ; React.createElement( "div", { className: "repository" }, React.createElement( "div", null, item.name ), React.createElement( "p", null, item.description ), React.createElement( "img", { src: item.owner.avatar_url } ) );

JSX çok basittir. Normal HTML kodu yazar ve küme parantezleri ekleyerek nesneden veri enjekte edersiniz. Köşeli parantez içindeki JavaScript kodu yürütülecek ve değer, ortaya çıkan DOM'ye eklenecektir. JSX'in avantajlarından biri, React'in değişiklikleri ve güncellemeleri izlemek için sanal bir DOM (sayfanın sanal bir temsili) oluşturmasıdır. React, tüm HTML'yi yeniden yazmak yerine, bilgiler her güncellendiğinde sayfanın DOM'sini değiştirir. Bu, React'in çözmek için yaratıldığı ana sorunlardan biridir.

jQuery yaklaşımı

Geliştiriciler jQuery'yi çok kullanırdı. Hala popüler olduğu ve saf JavaScript'te çözüme oldukça yakın olduğu için burada bahsetmek istiyorum. jQuery, DOM'yi sorgulayarak bir DOM düğümüne (veya bir DOM düğümleri koleksiyonuna) başvuru alır. Ayrıca, bu referansı içeriğini değiştirmek için çeşitli işlevlerle sarar.

jQuery'nin kendi DOM oluşturma araçları olsa da, doğada en sık gördüğüm şey yalnızca HTML birleştirmedir. Örneğin, html() işlevini çağırarak seçilen düğümlere HTML kodu ekleyebiliriz. jQuery belgelerine göre, bir div düğümünün içeriğini sınıf demo-container ile değiştirmek istiyorsak, bunu şöyle yapabiliriz:

 $( "div.demo-container" ).html( "<p>All new content.<em>You bet!</em></p>" );

Bu yaklaşım, DOM öğeleri oluşturmayı kolaylaştırır; ancak, düğümleri güncellememiz gerektiğinde, ihtiyaç duyduğumuz düğümleri sorgulamamız veya (daha yaygın olarak) bir güncelleme gerektiğinde tüm parçacığı yeniden oluşturmaya geri dönmemiz gerekir.

DOM API yaklaşımı

Tarayıcılardaki JavaScript, bir sayfadaki düğümleri oluşturmaya, değiştirmeye ve silmeye doğrudan erişim sağlayan yerleşik bir DOM API'sine sahiptir. Bu, React'in yaklaşımına yansır ve DOM API'sini kullanarak bu yaklaşımın faydalarına bir adım daha yaklaşırız. Yalnızca sayfanın gerçekten değiştirilmesi gereken öğelerini değiştiriyoruz. Ancak React, ayrı bir sanal DOM'nin kaydını da tutar. React, sanal ve gerçek DOM arasındaki farkları karşılaştırarak hangi parçaların değiştirilmesi gerektiğini belirleyebilir.

Bu ekstra adımlar bazen yararlıdır, ancak her zaman değil ve DOM'yi doğrudan değiştirmek daha verimli olabilir. Oluşturulan düğüme bir referans döndürecek olan _document.createElement_ işlevini kullanarak yeni DOM düğümleri oluşturabiliriz. Bu referansları takip etmek, yalnızca güncellenmesi gereken kısmı içeren düğümleri değiştirmenin kolay bir yolunu sunar.

JSX örneğindekiyle aynı yapıyı ve veri kaynağını kullanarak DOM'yi şu şekilde oluşturabiliriz:

 var item = document.createElement('div'); item.className = 'repository'; var nameNode = document.createElement('div'); nameNode.innerHTML = item.name item.appendChild(nameNode); var description = document.createElement('p'); description.innerHTML = item.description; item.appendChild(description ); var image = new Image(); Image.src = item.owner.avatar_url; item.appendChild(image); document.body.appendChild(item);

Aklınızdaki tek şey kod yürütmenin verimliliğiyse, bu yaklaşım çok iyidir. Bununla birlikte, verimlilik yalnızca yürütme hızıyla değil, aynı zamanda bakım kolaylığı, ölçeklenebilirlik ve plastisite ile de ölçülür. Bu yaklaşımla ilgili sorun, çok ayrıntılı ve bazen dolambaçlı olmasıdır. Sadece temel bir yapı inşa ediyor olsak bile, bir sürü fonksiyon çağrısı yazmamız gerekiyor. İkinci büyük dezavantaj, yaratılan ve izlenen çok sayıda değişkendir. Diyelim ki, üzerinde çalıştığınız bir bileşen kendi 30 DOM öğesini içeriyor, 30 farklı DOM öğesi ve değişkeni oluşturmanız ve kullanmanız gerekecek. Bazılarını yeniden kullanabilir ve sürdürülebilirlik ve plastisite pahasına biraz hokkabazlık yapabilirsiniz, ancak çok hızlı bir şekilde gerçekten dağınık hale gelebilir.

Bir diğer önemli dezavantaj, yazmanız gereken kod satırı sayısıdır. Zamanla, öğeleri bir ebeveynden diğerine taşımak giderek zorlaşır. Bu, React'ten gerçekten takdir ettiğim bir şey. JSX sözdizimini görüntüleyebilir ve birkaç saniye içinde hangi düğümün içerdiğini, nerede olduğunu ve gerekirse değiştirebilirim. Ve ilk başta önemli bir şey değilmiş gibi görünse de, çoğu projede daha iyi bir yol aramanızı sağlayacak sürekli değişiklikler vardır.

Önerilen çözüm

Doğrudan DOM ile çalışmak işe yarar ve işi bitirir, ancak özellikle HTML öznitelikleri eklememiz ve düğümleri yerleştirmemiz gerektiğinde, sayfanın oluşturulmasını çok ayrıntılı hale getirir. Bu nedenle fikir, JSX gibi teknolojilerle çalışmanın bazı faydalarını yakalamak ve hayatımızı daha basit hale getirmek olacaktır. Kopyalamaya çalıştığımız avantajlar şunlardır:

  1. DOM öğelerinin oluşturulmasının okunmasını ve değiştirilmesini kolaylaştırmak için HTML sözdiziminde kod yazın.
  2. React durumunda olduğu gibi bir sanal DOM eşdeğeri kullanmadığımız için ilgilendiğimiz düğümleri belirtmenin ve takip etmenin kolay bir yolunu bulmamız gerekiyor.

İşte bunu bir HTML parçacığı kullanarak gerçekleştirecek basit bir işlev.

 Browser.DOM = function (html, scope) { // Creates empty node and injects html string using .innerHTML // in case the variable isn't a string we assume is already a node var node; if (html.constructor === String) { var node = document.createElement('div'); node.innerHTML = html; } else { node = html; } // Creates of uses and object to which we will create variables // that will point to the created nodes var _scope = scope || {}; // Recursive function that will read every node and when a node // contains the var attribute add a reference in the scope object function toScope(node, scope) { var children = node.children; for (var iChild = 0; iChild < children.length; iChild++) { if (children[iChild].getAttribute('var')) { var names = children[iChild].getAttribute('var').split('.'); var obj = scope; while (names.length > 0) { var _property = names.shift(); if (names.length == 0) { obj[_property] = children[iChild]; } else { if (!obj.hasOwnProperty(_property)){ obj[_property] = {}; } obj = obj[_property]; } } } toScope(children[iChild], scope); } } toScope(node, _scope); if (html.constructor != String) { return html; } // If the node in the highest hierarchy is one return it if (node.childNodes.length == 1) { // if a scope to add node variables is not set // attach the object we created into the highest hierarchy node // by adding the nodes property. if (!scope) { node.childNodes[0].nodes = _scope; } return node.childNodes[0]; } // if the node in highest hierarchy is more than one return a fragment var fragment = document.createDocumentFragment(); var children = node.childNodes; // add notes into DocumentFragment while (children.length > 0) { if (fragment.append){ fragment.append(children[0]); }else{ fragment.appendChild(children[0]); } } fragment.nodes = _scope; return fragment; }

Fikir basit ama güçlü; oluşturmak istediğimiz HTML'yi string olarak fonksiyona gönderiyoruz, HTML string'inde bizim için referans oluşturulmasını istediğimiz düğümlere bir var niteliği ekliyoruz. İkinci parametre, bu referansların saklanacağı bir nesnedir. Belirtilmezse, döndürülen düğüm veya belge parçası üzerinde bir "nodes" özelliği oluşturacağız (en yüksek hiyerarşi düğümünün birden fazla olması durumunda). Her şey 60 satırdan daha az kodla gerçekleştirilir.

İşlev üç adımda çalışır:

  1. Yeni bir boş düğüm oluşturun ve tüm DOM yapısını oluşturmak için bu yeni düğümde innerHTML kullanın.
  2. Düğümler üzerinde yineleyin ve var özniteliği varsa, kapsam nesnesine bu özniteliğe sahip düğümü işaret eden bir özellik ekleyin.
  3. Hiyerarşide en üstteki düğümü veya birden fazla olması durumunda bir belge parçasını döndürün.

Peki, örneği oluşturma kodumuz şimdi nasıl görünüyor?

 var UI = {}; var template = ''; template += '<div class="repository">' template += ' <div var="name"></div>'; template += ' <p var="text"></p>' template += ' <img var="image"/>' template += '</div>'; var item = Browser.DOM(template, UI); UI.name.innerHTML = data.name; UI.text.innerHTML = data.description; UI.image.src = data.owner.avatar_url;

İlk olarak, oluşturulan düğümlere yapılan referansları depolayacağımız nesneyi (UI) tanımlarız. Daha sonra kullanacağımız HTML şablonunu bir dizge olarak oluşturuyoruz ve hedef düğümleri “var” niteliği ile işaretliyoruz. Ardından şablon ve referansları saklayacak boş nesne ile birlikte Browser.DOM fonksiyonunu çağırıyoruz. Son olarak, verileri düğümlerin içine yerleştirmek için saklanan referansları kullanırız.

Bu yaklaşım aynı zamanda DOM yapısını oluşturmayı ve verileri ayrı adımlara eklemeyi de ayırır, bu da kodun düzenli ve iyi yapılandırılmış olmasına yardımcı olur. Bu, DOM yapısını ayrı ayrı oluşturmamızı ve kullanılabilir olduğunda verileri doldurmamızı (veya güncellememizi) sağlar.

Çözüm

Bazılarımız çerçevelere geçme ve kontrolü devretme fikrinden hoşlanmasak da, bu çerçevelerin getirdiği faydaların farkında olmamız önemlidir. Bu kadar popüler olmalarının bir nedeni var.

Ve bir çerçeve her zaman tarzınıza veya ihtiyaçlarınıza uymayabilirken, bazı işlevler ve teknikler benimsenebilir, taklit edilebilir ve hatta bazen bir çerçeveden ayrılabilir. Bazı şeyler çeviride her zaman kaybolur, ancak bir çerçevenin taşıdığı maliyetin çok küçük bir bölümünde çok şey kazanılabilir ve kullanılabilir.