setState Ne Yapacağını Nasıl Biliyor?
2018 M12 9 • ☕️☕️ 9 min read
Translated by readers into: Español • Français • Português do Brasil • Türkçe • 日本語 • 简体中文 • 한국어
Read the original • Improve this translation • View all translated posts
setState bir component içerisinde çağırıldığında sizce neler oluyor?
import React from 'react';
import ReactDOM from 'react-dom';
class Button extends React.Component {
constructor(props) {
super(props);
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ clicked: true }); }
render() {
if (this.state.clicked) {
return <h1>Thanks</h1>;
}
return <button onClick={this.handleClick}>Click me!</button>;
}
}
ReactDOM.render(<Button />, document.getElementById('container'));
Tabi ki React, componenti bir sonraki { clicked: true }
state’i ile tekrar render ediyor ve DOM’u <h1>Thanks</h1>
elemanını gösterecek şekilde güncelliyor.
Gayet sıradan görünüyor. Ama bir dakika, bunu React mı yapıyor, yoksa React DOM mu?
DOM’u güncellemek React DOM’un sorumluluğu gibi görünüyor. Ama biz React DOM’da olmayan this.setState()
çağrısını yapıyoruz. Ve bizim React.Component base sınıfımız React içerisinde tanımlı.
Peki React.Component
içerisindeki setState()
nasıl DOM’u güncelliyor?
Önemli Not: React konusunda etkin olabilmek için, bu blog sitesindeki birçok diğer gönderi gibi, bu gönderide anlatılanları da bilmek zorunda değilsiniz. Bu gönderi perde arkasında işlerin nasıl yürüdüğünü görmek isteyenler için yazıldı. Tamamen opsiyonel.
React.Component
sınıfının DOM güncelleme işini yaptığını düşünüyor olabiliriz.
Ama eğer öyle olsaydı, this.setState()
farklı ortamlarda nasıl çalışacaktı? Örneğin, React Native componentleri de React.Component
’ten türüyor. Onlar da bizim yukarıda yaptığımız gibi this.setState()
çağrısını yapıyor, hatta React Native hem Android hem de IOS native bileşenleri ile çalışıyor.
React Test Renderer ve Shallow Renderer size tanıdık geliyor olabilir. Bu iki test stratejisi de normal componentleri render etmenizi ve this.setState()
’i çağırmanızı sağlıyor. Ama ikisi de DOM ile çalışmıyor.
Eger React Art gibi rendererlar kullandıysanız, sayfanızda birden fazla renderer kullanmanın mümkün olduğunu da biliyorsunuzdur. (Örneğin, ART componentleri bir React DOM ağacı üzerinde çalışır.) Bu global bir flag’i veya değişkeni kaçınılmaz hale getirir.
React.Component
state güncellemelerini bir şekilde platforma özel kod parçalarına aktarır. Bunun nasıl olduğunu anlamadan önce, paketlerin neden ve nasıl ayrıştırıldığı konusunda daha derine inelim.
React “engine”in react
paketi içerisinde yer aldığına dair genel bir yanılgı var, ama bu doğru değil.
Aslında, React 0.14 ile React paketleri ayrıştırıldığında, react
paketi özellikle sadece component tanımları için gerekli API’leri içerecek şekilde ayrıştırıldı. React’in büyük bir kısmı aslında React “renderer”lar içerisinde yaşıyor.
react-dom
, react-dom/server
, react-native
, react-test-renderer
, react-art
renderer örneklerinden birkaçı (siz de kendi renderer’inizi oluşturabilirsiniz).
İşte bu nedenle react
paketi hangi platformu hedeflediğinizden bagimsiz bir şekilde kullanışlı bir paket. react
paketinin bütün exportlari, React.Component
, React.createElement
, React.Children
gibi araçlar ve son olarak da Hooklar, hedef platformdan bağımsızlar. İster React DOM, ister React DOM Server ister React Native’de çalışıyor olsun, componentleriniz aynı şekilde import edilip ayni şekilde kullanılabilirler.
Bunun aksine, renderer paketleri platform özelinde API’ler sunar, bir DOM node üzerinde bir React hiyerarşisi oluşturmanızı sağlayan ReactDOM.render()
gibi. Her bir renderer bunun gibi API’ler sunar. İdealde, çoğu componentin bir renderer’dan herhangi bir şey import etmesine gerek yoktur. Bu onları daha taşınabilir hale getirir.
Bircok insanın React “engine” olarak düşündüğü şey aslında rendererlarin içerisinde yer alır. Birçok renderer aynı kodu içerir, bu koda “reconciler” diyoruz. Bir derleme adımı, daha iyi bir performans için, reconciler kodunu renderer kodu ile birleştirip tek bir paket haline getirir. (Kod kopyalamak genellikle paket boyutu açısından iyi değildir, fakat React kullanıcılarının büyük bir çoğunluğu aynı anda sadece bir renderer kullanıyor, react-dom
gibi).
Bu kısımdan öğrenmemiz gereken şey şu, react
paketi sizin React özelliklerini kullanmanızı sağlar, ama bunların nasıl gerçekleştirildikleri konusunda bilgisi yoktur. Renderer paketleri (react-dom
, react-native
vb) ise, platform bağımlı mantıkları ve React özelliklerinin gerçekleştirimini içerir. Bu kodun bir kısmı paylaşılır(“reconciler”), ama bu sadece rendererlarin gerçekleştirim detayı diyebiliriz.
Şimdi neden react
ve react-dom
paketlerinin yeni özellikler için güncellenmesi gerektiğini biliyoruz. Örneğin, React 16.3 ile gelen Context API özelliği, React.createContext()
özelliğini kullanılabilir hale getirdi.
Ama React.createContext()
aslında context özelliğinin gerçek geliştirmesine sahip değil. Örneğin gerçekleştirim, React DOM ve React DOM Server’da farklı şekilde olmak zorunda. Yani, createContext()
aslında sıradan bazı nesneler döndürüyor:
// A bit simplified
function createContext(defaultValue) {
let context = { _currentValue: defaultValue, Provider: null, Consumer: null };
context.Provider = {
$$typeof: Symbol.for('react.provider'),
_context: context,
};
context.Consumer = {
$$typeof: Symbol.for('react.context'),
_context: context,
};
return context;
}
Kodunuzda <MyContext.Provider>
ya da <MyContext.Consumer>
kullandığınızda, bu işlemin nasıl yapılmasına karar veren kısım renderer isimli kısımdır. React DOM bir şekilde context değerlerini takip edebilir, fakat React DOM server aynı işi farklı şekilde yapıyor olabilir.
react
paketini 16.3+ versiyonuna güncelleyip, react-dom
paketini güncellemediğinizde, henüz özel Provider
ve Consumer
türlerinden haberdar olmayan bir renderer kullanıyor olursunuz. Bu nedenle eski bir react-dom
versiyonu bu türlerin geçerli olmadığını söyleyerek hata verecektir.
Aynı uyarı React Native için de geçerlidir. Fakat, React DOM’un aksine, React güncellemesi React Native güncellemesini zorunlu kılmaz, React Native ile React bağımsız bir versiyon takvimine sahip. Güncellenen renderer kodu, React Native koduna birkaç haftada bir ayrı ayrı senkronize olur. Bu nedenle React DOM’da kullanılabilir hale gelen bazı özellikler React Native’de daha farklı bir zamanda kullanılır hale gelebilir.
Evet, şimdi react
paketinin ilginç bir şey içermediğini ve asıl gerçekleştirimin react-dom
, react-native
, gibi rendererlar içerisinde yer aldığını biliyoruz. Fakat bu bizim sorumuzun cevabı değil, React.Component
içerisindeki setState()
nasıl doğru rendererlar ile konuşuyor.
Cevap şu; her bir renderer oluşturulan sınıfta özel bir alana bir değer tanımlar. Bu alanın adı updater
. Bu sizin değiştirebileceğiniz bir değişken değil, daha çok React DOM, React DOM Server ya da React Native’in sınıfınızın bir nesnesini yarattığında değiştirdiği bir alan:
// Inside React DOM
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMUpdater;
// Inside React DOM Server
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMServerUpdater;
// Inside React Native
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactNativeUpdater;
React.Component
içerisindeki setState
gerçekleştirimine bakacak olursak, her yaptığının component nesnesini oluşturan renderer’a bu işi delege etmek olduğunu görürüz.
// A bit simplified
setState(partialState, callback) {
// Use the `updater` field to talk back to the renderer!
this.updater.enqueueSetState(this, partialState, callback);
}
React DOM Server bir state güncellemesini yoksayıp sizi uyarabilir, bunun yerine React DOM ve React Native ise kendi reconciler kopyalarına bu güncellemeyi yaptırabilir.
React paketinde tanımlanmış olmasına rağmen, this.setState()
, bu sayede DOM’u güncelleyebilir. React DOM tarafından belirlenen this.updater
‘i okur ve React DOM’un güncellemeyi ayarlayıp yapmasını sağlar.
Sınıfların bu işi nasıl yaptığını artık biliyoruz, peki Hooklar nasıl yapıyor?
İnsanlar Hooks aday API‘sini gördüklerinde sıklıkla şunu merak ettiler; useState
ne yapacağını nasıl biliyor? Varsayım onun temel sınıf React.Component
içindeki this.setState()
‘ten daha “sihirli” bir kod olduğuydu.
Fakat bugün gördüğümüz gibi, temel sınıftaki setState()
gerçekleştirimi bir illüzyondan daha fazlası değil. Gelen isteği mevcut renderer’a yönlendirmekten daha fazlasını yapmıyor. Ve useState
Hook da aslında tam olarak aynı şeyi yapıyor.
updater
alanı yerine, Hooklar “dispatcher” nesnesini kullanır. React.useState()
, React.useEffect()
ya da herhangi bir diğer Hook çağrısı yaptığınızda, bu çağrılar mevcut dispatcher’a iletilir.
// In React (simplified a bit)
const React = {
// Real property is hidden a bit deeper, see if you can find it!
__currentDispatcher: null,
useState(initialState) {
return React.__currentDispatcher.useState(initialState);
},
useEffect(initialState) {
return React.__currentDispatcher.useEffect(initialState);
},
// ...
};
Ve her bir renderer componenti render etmeden önce dispatcher’i tanimlar.
// In React DOM
const prevDispatcher = React.__currentDispatcher;
React.__currentDispatcher = ReactDOMDispatcher;let result;
try {
result = YourComponent(props);
} finally {
// Restore it back React.__currentDispatcher = prevDispatcher;}
Örnek olarak buradaki React DOM Server gerçekleştirimini ve buradaki React DOM ve React Native tarafından paylaşılan reconciler gerçekleştirimini verebiliriz.
react-dom
gibi rendererlarin, kullanılan Hooklar ile aynı pakete erişmesinin sebebi budur. Yoksa, componentiniz gerekli dispatcher’i bulamayacaktır. Bu durum ayni component ağacından birden fazla React kopyası bulundurduğunuzda çalışmayabilir. Fakat, bu durum zaten anlaşılması güç hatalara yol acıyordu, Hooklar sizi farklı paket kullanımı size bir probleme sebep olmadan bu durumu düzeltmeye zorluyor.
Her ne kadar bunu yapmanızı tavsiye etmesek de, teknik olarak dispatcher davranışını üst seviye araçlar gibi kullanım durumlarında, isteğinize göre değiştirmeniz mümkün. (__currentDispatcher
ismi konusunda yalan söyledim, fakat gerçek ismi React repo’sunda bulabilirsiniz.) Örneğin React DevTools, Hook ağacının Javascript yığıt izlerini bularak, iç gözlem yapabilmek için kendine özel tasarlanmış bir dispatcher kullanacak. Bunu evde denemeyin.
Bu aynı zamanda Hooklarin, doğuştan React’a bağlı olmadığını gösteriyor. Eğer ileride bir çok kütüphane aynı temel Hooklari tekrar kullanmak isterse, teorik olarak dispatcher farklı bir pakete taşınabilir ve birinci-sınıf bir API olarak daha az “korkutucu” bir isimle dışa açılabilir. Pratikte, olgunlaşmamış soyutlamayı gerçekten bir ihtiyaç olmadıkça yapmamayı tercih ediyoruz.
Hem updater
alanı hem de __currentDispatcher
nesnesi genel programlama prensiplerinden biri olan dependency injection prensibinin bir türüdür. İki senaryoda da, rendererlar componentlerinizi daha declarative hale getirmek için, setState
gerçekleştirimini genel React paketine “enjekte” ederler.
React kullanırken bunun nasıl çalıştığı konusunu düşünmenize gerek yoktur. Biz React kullanıcılarının dependency injection gibi soyut kavramlardan daha çok, kendi uygulamalarının koduyla ilgili düşünmesini sağlamaya çalışıyoruz. Fakat this.setState()
ya da useState()
nasıl ne yapacağını biliyor konusunu merak ettiyseniz umarım bu yazı size yardımcı olmuştur.