Bileşenler
Yapı
Bileşenler, kodu düzenlemeyi ve/veya yeniden kullanmayı kolaylaştırmak için bir görünümün parçalarını kapsülleme mekanizmasıdır.
view
metodu olan herhangi bir JavaScript nesnesi, bir Mithril.js bileşenidir. Bileşenler, m()
fonksiyonu aracılığıyla kullanılabilir:
// Bileşeni tanımla
var Example = {
view: function (vnode) {
return m('div', 'Merhaba');
},
};
// Bileşeni kullan
m(Example);
// eşdeğer HTML
// <div>Merhaba</div>
Yaşam Döngüsü Metotları
Bileşenler, sanal DOM düğümleriyle aynı yaşam döngüsü metotlarına sahip olabilir. vnode
'un her yaşam döngüsü metoduna ve view
metoduna argüman olarak iletildiğini unutmayın (onbeforeupdate
'e ek olarak önceki vnode ile birlikte):
var ComponentWithHooks = {
oninit: function (vnode) {
console.log('başlatıldı');
},
oncreate: function (vnode) {
console.log('DOM oluşturuldu');
},
onbeforeupdate: function (newVnode, oldVnode) {
return true;
},
onupdate: function (vnode) {
console.log('DOM güncellendi');
},
onbeforeremove: function (vnode) {
console.log('çıkış animasyonu başlayabilir');
return new Promise(function (resolve) {
// animasyon tamamlandıktan sonra çağır
resolve();
});
},
onremove: function (vnode) {
console.log('DOM öğesi kaldırılıyor');
},
view: function (vnode) {
return 'merhaba';
},
};
Diğer sanal DOM düğümü türleri gibi, bileşenler de vnode türleri olarak kullanıldıklarında ek yaşam döngüsü metotlarına sahip olabilir.
function initialize(vnode) {
console.log('vnode olarak başlatıldı');
}
m(ComponentWithHooks, { oninit: initialize });
Vnode'lardaki yaşam döngüsü metotları, bileşen metotlarını geçersiz kılmaz; bileşen metotları da vnode metotlarını geçersiz kılmaz. Bileşen yaşam döngüsü metotları her zaman vnode'un karşılık gelen metodundan sonra çalıştırılır.
Vnode'lardaki kendi geri çağırma fonksiyon adlarınız için yaşam döngüsü metot adlarını kullanmamaya dikkat edin.
Yaşam döngüsü metotları hakkında daha fazla bilgi edinmek için yaşam döngüsü metotları sayfasına bakın.
Bileşenlere Veri Aktarma
Veri aktarmak için, hyperscript fonksiyonunda ikinci parametre olarak bir attrs
nesnesi kullanılabilir.
m(Example, { name: 'Floyd' });
Bu verilere, bileşenin görünümünde veya yaşam döngüsü metotlarında vnode.attrs
üzerinden erişilebilir:
var Example = {
view: function (vnode) {
return m('div', 'Merhaba, ' + vnode.attrs.name);
},
};
DİKKAT: Yaşam döngüsü metotları attrs
nesnesinde de tanımlanabilir, bu nedenle kendi geri çağırmalarınız için bu adları kullanmaktan kaçınmalısınız, çünkü bunlar Mithril.js tarafından da çağrılır. Bunları attrs
içinde yalnızca yaşam döngüsü metotları olarak kullanmak istediğinizde kullanın.
Durum (State)
Tüm sanal DOM düğümleri gibi, bileşen vnode'ları da duruma sahip olabilir. Bileşen durumu, nesne yönelimli mimarileri desteklemek, kapsülleme sağlamak ve sorumlulukları ayırmak için faydalıdır.
Diğer birçok framework'ten farklı olarak, bileşen durumunu değiştirmek yeniden çizimleri veya DOM güncellemelerini tetiklemez. Bunun yerine, yeniden çizimler olay işleyicileri tetiklendiğinde, m.request tarafından yapılan HTTP istekleri tamamlandığında veya tarayıcı farklı rotalara gittiğinde gerçekleştirilir. Mithril.js'nin bileşen durumu mekanizmaları, uygulamalar için yalnızca bir kolaylık olarak mevcuttur.
Yukarıdaki koşulların herhangi birinin sonucu olmayan bir durum değişikliği meydana gelirse (örneğin, bir setTimeout
'tan sonra), yeniden çizimi manuel olarak tetiklemek için m.redraw()
'u kullanabilirsiniz.
Kapanış (Closure) Bileşen Durumu
Yukarıdaki örneklerdeki bileşenler, Mithril.js tarafından dahili olarak örneklerinin prototipi olarak kullanılan bir POJO (Plain Old JavaScript Object) olarak tanımlanır. POJO, "Düz Eski JavaScript Nesnesi" anlamına gelir. Bir POJO ile bileşen durumunu kullanmak mümkündür (aşağıda tartışacağımız gibi), ancak bu en temiz veya en basit yaklaşım değildir. Bunun için, POJO bileşen örneğini döndüren ve kendi kapalı kapsamını taşıyan bir kapanış bileşeni kullanacağız. Kapanış bileşenleri, basit bir sarmalayıcı fonksiyondur.
Bir kapanış bileşeni ile durum, basitçe dış fonksiyonda bildirilen değişkenler tarafından korunabilir:
function ComponentWithState(initialVnode) {
// Her örneğe özgü bileşen durumu değişkeni
var count = 0;
// POJO bileşen örneği: bir vnode döndüren bir
// view fonksiyonuna sahip herhangi bir nesne
return {
oninit: function (vnode) {
console.log('bir kapanış bileşeni başlat');
},
view: function (vnode) {
return m(
'div',
m('p', 'Sayım: ' + count),
m(
'button',
{
onclick: function () {
count += 1;
},
},
'Sayımı artır'
)
);
},
};
}
Kapanış içinde tanımlanan fonksiyonlar da durum değişkenlerine erişebilir.
function ComponentWithState(initialVnode) {
var count = 0;
function increment() {
count += 1;
}
function decrement() {
count -= 1;
}
return {
view: function (vnode) {
return m(
'div',
m('p', 'Sayım: ' + count),
m(
'button',
{
onclick: increment,
},
'Artır'
),
m(
'button',
{
onclick: decrement,
},
'Azalt'
)
);
},
};
}
Kapanış bileşenleri, POJO'lar gibi kullanılır; örneğin: m(ComponentWithState, { passedData: ... })
.
Kapanış bileşenlerinin büyük bir avantajı, olay işleyici geri çağırmalarını bağlarken "this" hakkında endişelenmeye gerek olmamasıdır. Çünkü "this" hiç kullanılmaz ve bağlam belirsizliği ortadan kalkar.
POJO Bileşen Durumu
Genel olarak, bileşen durumunu yönetmek için kapanışları kullanmanız önerilir. Ancak, bir POJO'da durumu yönetmek için bir nedeniniz varsa, bir bileşenin durumuna üç şekilde erişilebilir: başlatma sırasında bir plan olarak, vnode.state
aracılığıyla ve bileşen metotlarında this
anahtar kelimesi aracılığıyla.
Başlatma Sırasında
POJO bileşenleri için, bileşen nesnesi her bileşen örneğinin prototipidir, bu nedenle bileşen nesnesinde tanımlanan herhangi bir özellik vnode.state
'in bir özelliği olarak erişilebilir olacaktır. Bu, basit "plan" durumu başlatmasına izin verir.
Aşağıdaki örnekte, data
, ComponentWithInitialState
bileşeninin vnode.state
nesnesinin bir özelliği haline gelir.
var ComponentWithInitialState = {
data: 'Başlangıç içeriği',
view: function (vnode) {
return m('div', vnode.state.data);
},
};
m(ComponentWithInitialState);
// Eşdeğer HTML
// <div>Başlangıç içeriği</div>
vnode.state Aracılığıyla
Gördüğünüz gibi, duruma bir bileşenin tüm yaşam döngüsü metotlarına ve view
metoduna sunulan vnode.state
özelliği aracılığıyla da erişilebilir.
var ComponentWithDynamicState = {
oninit: function (vnode) {
vnode.state.data = vnode.attrs.text;
},
view: function (vnode) {
return m('div', vnode.state.data);
},
};
m(ComponentWithDynamicState, { text: 'Merhaba' });
// Eşdeğer HTML
// <div>Merhaba</div>
this Anahtar Kelimesi Aracılığıyla
Duruma, bir bileşenin tüm yaşam döngüsü metotlarına ve view
metoduna sunulan this
anahtar kelimesi aracılığıyla da erişilebilir.
var ComponentUsingThis = {
oninit: function (vnode) {
this.data = vnode.attrs.text;
},
view: function (vnode) {
return m('div', this.data);
},
};
m(ComponentUsingThis, { text: 'Merhaba' });
// Eşdeğer HTML
// <div>Merhaba</div>
ES5 fonksiyonlarını kullanırken, iç içe anonim fonksiyonlardaki this
değerinin bileşen örneği olmadığının farkında olun. Bu JavaScript sınırlamasının üstesinden gelmenin iki önerilen yolu vardır: ok fonksiyonlarını kullanın veya bunlar desteklenmiyorsa vnode.state
kullanın.
Sınıflar
İhtiyaçlarınıza uygunsa (örneğin, nesne yönelimli projelerde), bileşenler sınıflar kullanılarak da yazılabilir:
class ClassComponent {
constructor(vnode) {
this.kind = 'sınıf bileşeni';
}
view() {
return m('div', `Merhaba, ben bir ${this.kind}`);
}
oncreate() {
console.log(`Bir ${this.kind} oluşturuldu`);
}
}
Sınıf bileşenleri, ağacın oluşturulması için .prototype.view
aracılığıyla algılanan bir view()
metodu tanımlamalıdır.
Bu bileşenler, normal bileşenler gibi kullanılabilir.
// ÖRNEK: m.render aracılığıyla
m.render(document.body, m(ClassComponent));
// ÖRNEK: m.mount aracılığıyla
m.mount(document.body, ClassComponent);
// ÖRNEK: m.route aracılığıyla
m.route(document.body, '/', {
'/': ClassComponent,
});
// ÖRNEK: bileşen kompozisyonu
class AnotherClassComponent {
view() {
return m('main', [m(ClassComponent)]);
}
}
Sınıf Bileşen Durumu
Sınıflarla, durum sınıf örneği özellikleri ve metotları tarafından yönetilebilir ve this
aracılığıyla erişilebilir:
class ComponentWithState {
constructor(vnode) {
this.count = 0;
}
increment() {
this.count += 1;
}
decrement() {
this.count -= 1;
}
view() {
return m(
'div',
m('p', 'Sayım: ', this.count),
m(
'button',
{
onclick: () => {
this.increment();
},
},
'Artır'
),
m(
'button',
{
onclick: () => {
this.decrement();
},
},
'Azalt'
)
);
}
}
this
bağlamına doğru şekilde başvurulabilmesi için olay işleyici geri çağırımları için ok fonksiyonlarını kullanmamız gerektiğini unutmayın.
Bileşen Türlerini Karıştırma
Bileşenler serbestçe karıştırılabilir. Bir sınıf bileşeni, kapanış veya POJO bileşenlerine çocuk olarak sahip olabilir, vb...
Özel Nitelikler
Mithril.js, çeşitli özellik anahtarlarına özel anlamlar yükler. Bu nedenle, bu anahtarları normal bileşen özelliklerinde kullanmaktan kaçınmalısınız.
- Yaşam döngüsü metotları:
oninit
,oncreate
,onbeforeupdate
,onupdate
,onbeforeremove
veonremove
- Anahtarlı parçalarda kimliği izlemek için kullanılan
key
- Vnode'ları normal nitelik nesnelerinden ve vnode olmayan diğer şeylerden ayırmak için kullanılan
tag
.
Anti-pattern'lerden Kaçının
Mithril.js esnek olmasına rağmen, bazı kod pattern'leri önerilmez:
Şişman Bileşenlerden Kaçının
Genel olarak, "şişman" bir bileşen, özel örnek metotlarına sahip bir bileşendir. Başka bir deyişle, fonksiyonları vnode.state
'e veya this
'e eklemekten kaçınmalısınız. Mantıksal olarak bir bileşen örneği metoduna uyan ve diğer bileşenler tarafından yeniden kullanılamayan bir mantığa sahip olmak son derece nadirdir. Söz konusu mantığın gelecekte farklı bir bileşen tarafından ihtiyaç duyulması nispeten yaygındır.
Bu mantık, bileşen durumuna bağlıysa, veri katmanına taşındığında kodu yeniden düzenlemek daha kolay olacaktır.
Bu şişman bileşeni düşünün:
// views/Login.js
// KAÇININ
var Login = {
username: '',
password: '',
setUsername: function (value) {
this.username = value;
},
setPassword: function (value) {
this.password = value;
},
canSubmit: function () {
return this.username !== '' && this.password !== '';
},
login: function () {
/*...*/
},
view: function () {
return m('.login', [
m('input[type=text]', {
oninput: function (e) {
this.setUsername(e.target.value);
},
value: this.username,
}),
m('input[type=password]', {
oninput: function (e) {
this.setPassword(e.target.value);
},
value: this.password,
}),
m(
'button',
{ disabled: !this.canSubmit(), onclick: this.login },
'Giriş'
),
]);
},
};
Normalde, daha büyük bir uygulama bağlamında yukarıdaki gibi bir giriş bileşeninin, kullanıcı kaydı ve şifre kurtarma bileşenleriyle birlikte bulunması beklenir. Giriş ekranından kayıt veya şifre kurtarma ekranlarına (veya tam tersi) giderken e-posta alanını önceden doldurabilmek istediğimizi hayal edin, böylece kullanıcı yanlış sayfayı doldurmuşsa e-postasını yeniden yazmasına gerek kalmaz (veya belki de bir kullanıcı adı bulunamazsa kullanıcıyı kayıt formuna yönlendirmek istersiniz).
Hemen fark ediyoruz ki, bu bileşendeki "username" ve "password" alanlarını başka bir bileşenle paylaşmak zor. Çünkü şişman bileşen, durumunu kapsüller ve bu da dışarıdan erişimi zorlaştırır.
Bu bileşeni yeniden düzenlemek ve durum kodunu bileşenden çıkarıp uygulamanın veri katmanına çekmek daha mantıklıdır. Bu, yeni bir modül oluşturmak kadar basit olabilir:
// models/Auth.js
// TERCİH EDİN
var Auth = {
username: '',
password: '',
setUsername: function (value) {
Auth.username = value;
},
setPassword: function (value) {
Auth.password = value;
},
canSubmit: function () {
return Auth.username !== '' && Auth.password !== '';
},
login: function () {
/*...*/
},
};
module.exports = Auth;
Ardından, bileşeni temizleyebiliriz:
// views/Login.js
// TERCİH EDİN
var Auth = require('../models/Auth');
var Login = {
view: function () {
return m('.login', [
m('input[type=text]', {
oninput: function (e) {
Auth.setUsername(e.target.value);
},
value: Auth.username,
}),
m('input[type=password]', {
oninput: function (e) {
Auth.setPassword(e.target.value);
},
value: Auth.password,
}),
m(
'button',
{
disabled: !Auth.canSubmit(),
onclick: Auth.login,
},
'Giriş'
),
]);
},
};
Bu sayede "Auth" modülü, kimlik doğrulama ile ilgili durumun kaynağı haline gelir. "Register" bileşeni de bu verilere kolayca erişebilir ve gerekirse "canSubmit" gibi metotları yeniden kullanabilir. Ayrıca, doğrulama kodu gerektiğinde (örneğin, e-posta alanı için), sadece "setEmail" metodunu değiştirmeniz yeterli olacaktır. Bu değişiklik, e-posta alanını değiştiren tüm bileşenler için e-posta doğrulamasını otomatik olarak uygulayacaktır.
Ek bir bonus olarak, bileşenin olay işleyicileri için duruma bir referans tutmak için artık .bind
kullanmamıza gerek kalmadığını unutmayın.
vnode.attrs
'ın Kendisini Diğer Vnode'lara İletmeyin
Bazen, bir arayüzü esnek tutmak ve uygulamayı basitleştirmek için özellikleri belirli bir alt bileşene veya öğeye iletmek isteyebilirsiniz. Örneğin, Bootstrap'ın modal'ı gibi. Bir vnode'un niteliklerini şu şekilde iletmek cazip gelebilir:
// KAÇININ
var Modal = {
// ...
view: function (vnode) {
return m('.modal[tabindex=-1][role=dialog]', vnode.attrs, [
// `vnode.attrs` burada iletiliyor ^
// ...
]);
},
};
Yukarıdaki gibi yaparsanız, kullanırken sorunlarla karşılaşabilirsiniz:
var MyModal = {
view: function () {
return m(
Modal,
{
// Bu iki kez değiştirir, bu yüzden göstermez
onupdate: function (vnode) {
if (toggle) $(vnode.dom).modal('toggle');
},
},
[
// ...
]
);
},
};
Bunun yerine, "tek" nitelikleri vnode'lara iletmelisiniz:
// TERCİH EDİN
var Modal = {
// ...
view: function (vnode) {
return m('.modal[tabindex=-1][role=dialog]', vnode.attrs.attrs, [
// `attrs:` burada iletiliyor ^
// ...
]);
},
};
// Örnek
var MyModal = {
view: function () {
return m(Modal, {
attrs: {
// Bu bir kez değiştirir
onupdate: function (vnode) {
if (toggle) $(vnode.dom).modal('toggle');
},
},
// ...
});
},
};
children
'ı Manipüle Etmeyin
Eğer bir bileşen, özellikleri veya alt öğeleri nasıl uygulayacağı konusunda belirli kurallara sahipse, özel özellikler kullanmaya geçmelisiniz.
Genellikle, örneğin bir bileşenin yapılandırılabilir bir başlığı ve gövdesi varsa, birden çok çocuk kümesi tanımlamak arzu edilir.
Bu amaçla children
özelliğini yapılandırmaktan kaçının.
// KAÇININ
var Header = {
view: function (vnode) {
return m('.section', [
m('.header', vnode.children[0]),
m('.tagline', vnode.children[1]),
]);
},
};
m(Header, [m('h1', 'Başlığım'), m('h2', 'Lorem ipsum')]);
// garip kullanım örneği
m(Header, [
[m('h1', 'Başlığım'), m('small', 'Küçük bir not')],
m('h2', 'Lorem ipsum'),
]);
Yukarıdaki bileşen, alt öğelerin alındığı sırayla ve bitişik bir şekilde çıktılanacağı varsayımını ihlal eder. Bu nedenle, kodunu okumadan bileşeni anlamak zordur. Bunun yerine, nitelikleri isimlendirilmiş parametreler olarak kullanın ve children
'ı tek tip çocuk içeriği için ayırın:
// TERCİH EDİN
var BetterHeader = {
view: function (vnode) {
return m('.section', [
m('.header', vnode.attrs.title),
m('.tagline', vnode.attrs.tagline),
]);
},
};
m(BetterHeader, {
title: m('h1', 'Başlığım'),
tagline: m('h2', 'Lorem ipsum'),
});
// daha net kullanım örneği
m(BetterHeader, {
title: [m('h1', 'Başlığım'), m('small', 'Küçük bir not')],
tagline: m('h2', 'Lorem ipsum'),
});
Bileşenleri Statik Olarak Tanımlayın, Dinamik Olarak Çağırın
Görünümlerin İçinde Bileşen Tanımları Oluşturmaktan Kaçının
Bir bileşeni bir view
metodu içinden oluşturursanız (doğrudan satır içi veya bunu yapan bir fonksiyonu çağırarak), her yeniden çizim bileşenin farklı bir klonuna sahip olacaktır. Bileşen vnode'larını karşılaştırırken, yeni vnode tarafından başvurulan bileşen, eski bileşen tarafından başvurulanla kesinlikle eşit değilse, iki bileşenin sonuçta eşdeğer kod çalıştırsalar bile farklı bileşenler olduğu varsayılır. Bu, bir fabrika aracılığıyla dinamik olarak oluşturulan bileşenlerin her zaman sıfırdan yeniden oluşturulacağı anlamına gelir.
Bu nedenle, bileşenleri tekrar tekrar oluşturmaktan kaçınmalısınız. Bunun yerine, bileşenleri doğru ve uygun bir şekilde kullanın.
// KAÇININ
var ComponentFactory = function (greeting) {
// her çağrıda yeni bir bileşen oluşturur
return {
view: function () {
return m('div', greeting);
},
};
};
m.render(document.body, m(ComponentFactory('merhaba')));
// ikinci kez çağırmak, hiçbir şey yapmamaktansa div'i sıfırdan yeniden oluşturur
m.render(document.body, m(ComponentFactory('merhaba')));
// TERCİH EDİN
var Component = {
view: function (vnode) {
return m('div', vnode.attrs.greeting);
},
};
m.render(document.body, m(Component, { greeting: 'merhaba' }));
// ikinci kez çağırmak DOM yapısını değiştirmez
m.render(document.body, m(Component, { greeting: 'merhaba' }));
Görünümlerin Dışında Bileşen Örnekleri Oluşturmaktan Kaçının
Tersine, benzer nedenlerle, bir bileşen örneği bir görünümün dışında oluşturulursa, gelecekteki yeniden çizimler düğüm üzerinde bir eşitlik kontrolü yapacak ve atlayacaktır. Bu nedenle bileşen örnekleri her zaman görünümlerin içinde oluşturulmalıdır:
// KAÇININ
var Counter = {
count: 0,
view: function (vnode) {
return m(
'div',
m('p', 'Sayım: ' + vnode.state.count),
m(
'button',
{
onclick: function () {
vnode.state.count++;
},
},
'Sayımı artır'
)
);
},
};
var counter = m(Counter);
m.mount(document.body, {
view: function (vnode) {
return [m('h1', 'Uygulamam'), counter];
},
});
Yukarıdaki örnekte, sayaç bileşeni düğmesine tıklamak sayaç değerini artıracaktır, ancak bileşeni temsil eden vnode aynı referansı paylaştığı ve bu nedenle oluşturma işlemi bunları karşılaştırmadığı için görünümü tetiklenmeyecektir. Yeni bir vnode oluşturulduğundan emin olmak için bileşenleri her zaman görünümde çağırmanız gerekir:
// TERCİH EDİN
var Counter = {
count: 0,
view: function (vnode) {
return m(
'div',
m('p', 'Sayım: ' + vnode.state.count),
m(
'button',
{
onclick: function () {
vnode.state.count++;
},
},
'Sayımı artır'
)
);
},
};
m.mount(document.body, {
view: function (vnode) {
return [m('h1', 'Uygulamam'), m(Counter)];
},
});