Virtuális DOM csomópontok
Mi az a virtuális DOM?
A virtuális DOM fa egy JavaScript adatstruktúra, amely a DOM fa leírására szolgál. Egymásba ágyazott virtuális DOM csomópontokból, más néven vnode-okból áll.
Amikor egy virtuális DOM fát először renderelünk, az egy tervrajzként szolgál a struktúrájának megfelelő DOM fa létrehozásához.
A virtuális DOM fákat általában minden renderelési ciklusban újra létrehozzuk. A Mithril.js összehasonlítja (diff-eli) a vnode fát az előző verziójával, és csak azokon a helyeken módosítja a DOM elemeket, ahol változások történtek.
Feleslegesnek tűnhet a vnode-ok ilyen gyakori újra létrehozása, de a modern JavaScript motorok kevesebb mint egy milliszekundum alatt több százezer objektumot képesek létrehozni. Másrészt a DOM módosítása nagyságrendekkel költségesebb, mint a vnode-ok létrehozása.
Ezért a Mithril.js egy kifinomult és erősen optimalizált virtuális DOM diff algoritmust használ a DOM frissítések minimalizálására. A Mithril.js emellett gondosan megtervezett vnode adatstruktúrákat hoz létre, amelyeket a JavaScript motorok natívhoz közeli adatstruktúra-hozzáférési teljesítményre fordítanak le. Ezenkívül a Mithril.js agresszíven optimalizálja a vnode-okat létrehozó függvényt is.
A Mithril.js azért fektet be ilyen nagy erőfeszítéseket egy olyan renderelési modell támogatásába, amely minden rendereléskor újra létrehozza a teljes virtuális DOM fát, hogy egy deklaratív azonnali módú API-t biztosítson, egy olyan renderelési stílust, amely drasztikusan megkönnyíti a felhasználói felület összetettségének kezelését.
Annak szemléltetésére, hogy miért olyan fontos az azonnali mód, vegyük figyelembe a DOM API-t és a HTML-t. A DOM API egy imperatív megtartott módú API, és megköveteli 1. a pontos utasítások kiírását egy DOM fa eljárási összeállításához, és 2. további utasítások kiírását a fa frissítéséhez. A DOM API imperatív jellege azt jelenti, hogy sok lehetőség van a kód mikro-optimalizálására, de ez azt is jelenti, hogy nagyobb a valószínűsége a hibák bevezetésének és a kód nehezebben érthetővé tételének.
Ezzel szemben a HTML közelebb áll egy azonnali módú renderelési rendszerhez. A HTML segítségével egy DOM fát sokkal természetesebb és olvashatóbb módon írhatunk le, anélkül, hogy aggódnunk kellene amiatt, hogy elfelejtünk egy gyermeket egy szülőhöz fűzni, stack overflow-kba ütközünk rendkívül mély fák renderelésekor stb.
A virtuális DOM egy lépéssel tovább megy a HTML-nél azáltal, hogy lehetővé teszi dinamikus DOM fák írását anélkül, hogy manuálisan kellene több DOM API hívást írnunk a felhasználói felület hatékony szinkronizálásához tetszőleges adatváltozásokhoz.
Alapok
A virtuális DOM csomópontok, vagy vnode-ok, JavaScript objektumok, amelyek DOM elemeket (vagy a DOM részeinek) képviselnek. A Mithril.js virtuális DOM motorja egy vnode fa alapján hoz létre egy DOM fát.
A vnode-ok a m()
hyperscript segédeszközzel jönnek létre:
m('div', { id: 'test' }, 'hello');
A Hyperscript komponenseket is felhasználhat:
// komponens definiálása
var ExampleComponent = {
view: function (vnode) {
return m('div', vnode.attrs, ['Hello ', vnode.children]);
},
};
// felhasználása
m(ExampleComponent, { style: 'color:red;' }, 'world');
// ekvivalens HTML:
// <div style="color:red;">Hello world</div>
Struktúra
A virtuális DOM csomópontok, vagy vnode-ok, JavaScript objektumok, amelyek egy elemet (vagy a DOM részeinek) képviselnek, és a következő tulajdonságokkal rendelkeznek:
Tulajdonság | Típus | Leírás |
---|---|---|
tag | String|Object | Egy DOM elem nodeName tulajdonsága. Lehet a [ karakterlánc is, ha a vnode egy fragment, # , ha egy szöveges vnode, vagy < , ha egy biztonságos HTML vnode. Ezenkívül lehet egy komponens is. |
key | String? | Az az érték, amellyel egy DOM elemet egy adathalmaz megfelelő eleméhez rendelünk. |
attrs | Object? | DOM attribútumok, események, tulajdonságok és életciklus metódusok hashmap-je. |
children | (Array|String|Number|Boolean)? | A legtöbb vnode típusban a children tulajdonság vnode-ok tömbje. Szöveges és biztonságos HTML vnode-ok esetén a children tulajdonság vagy egy string, egy szám vagy egy boolean. |
text | (String|Number|Boolean)? | Ezt a children helyett használjuk, ha egy vnode egy szöveges csomópontot tartalmaz egyetlen gyermekként. Ez teljesítménybeli okokból történik. A komponens vnode-ok soha nem használják a text tulajdonságot, még akkor sem, ha egy szöveges csomópontjuk van egyetlen gyermekként. |
dom | Element? | Arra az elemre mutat, amely a vnode-nak felel meg. Ez a tulajdonság az oninit életciklus metódusban undefined . Fragmentekben és biztonságos HTML vnode-okban a dom a tartomány első elemére mutat. |
domSize | Number? | Ez csak fragmentekben és biztonságos HTML vnode-okban van beállítva, és minden más vnode típusban undefined . Meghatározza a DOM elemek számát, amelyet a vnode képvisel (a dom tulajdonság által hivatkozott elemtől kezdve). |
state | Object? | Egy objektum, amely a renderelések között megmarad. A mag biztosítja, amikor szükséges. A POJO komponens vnode-ok esetén a state prototipikusan öröklődik a komponens objektumából/osztályából. Az osztályalapú komponens vnode-okban az osztály egy példánya. A closure komponensekben a closure által visszaadott objektum. |
events | Object? | Egy objektum, amely a renderelések között megmarad, és amely eseménykezelőket tárol, hogy azok a DOM API segítségével eltávolíthatók legyenek. Az events tulajdonság undefined , ha nincsenek definiálva eseménykezelők. Ezt a tulajdonságot csak a Mithril.js használja belsőleg, ne használd vagy módosítsd. |
instance | Object? | Komponensek esetén a view által visszaadott érték tárolási helye. Ezt a tulajdonságot csak a Mithril.js használja belsőleg, ne használd vagy módosítsd. |
Vnode típusok
A vnode tag
tulajdonsága határozza meg a típusát. Öt vnode típus létezik:
Vnode típus | Példa | Leírás |
---|---|---|
Elem | {tag: "div"} | Egy DOM elemet képvisel. |
Fragment | {tag: "[", children: []} | DOM elemek listáját képviseli, amelyek szülő DOM eleme más elemeket is tartalmazhat, amelyek nem a fragmentben vannak. A m() segédfüggvény használatakor a fragment vnode-ok csak úgy hozhatók létre, ha tömböket ágyazunk be a m() children paraméterébe. Az m("[") nem hoz létre érvényes vnode-ot. |
Szöveg | {tag: "#", children: ""} | Egy DOM szöveges csomópontot képvisel. |
Megbízható HTML | {tag: "<", children: "<br>"} | DOM elemek listáját képviseli egy HTML karakterláncból. |
Komponens | {tag: ExampleComponent} | Ha a tag egy JavaScript objektum egy view metódussal, akkor a vnode a komponens renderelésével generált DOM-ot képviseli. |
A virtuális DOM fában minden vnode, beleértve a szöveget is. Az m()
segédeszköz automatikusan normalizálja a children
argumentumát, és a stringeket szöveges vnode-okká, a beágyazott tömböket pedig fragment vnode-okká alakítja.
Csak az elem tag nevek és a komponensek lehetnek az m()
függvény első argumentumai. Más szavakkal, a [
, #
és <
nem érvényes selector
argumentumok az m()
számára. A biztonságos HTML vnode-ok a m.trust()
segítségével hozhatók létre.
Monomorf osztály
A mithril/render/vnode
modult a Mithril.js használja az összes vnode generálásához. Ez biztosítja, hogy a modern JavaScript motorok optimalizálhassák a virtuális dom diffing-et azáltal, hogy a vnode-okat mindig ugyanabba a rejtett osztályba fordítják.
Amikor vnode-okat létrehozó könyvtárakat fejlesztünk, a magas szintű renderelési teljesítmény biztosítása érdekében ezt a modult használjuk a csupasz JavaScript objektumok írása helyett.
Kerülje az anti-pattern-eket
Kerülje a vnode-ok újrafelhasználását
A vnode-oknak a DOM állapotát kell képviselniük egy adott időpontban. A Mithril.js renderelő motorja azt feltételezi, hogy egy újrafelhasznált vnode változatlan, ezért egy korábbi renderelésben használt vnode módosítása definiálatlan viselkedést eredményez.
Lehetséges a vnode-ok helyben történő újrafelhasználása a diff megakadályozása érdekében, de előnyösebb az onbeforeupdate
használata.
Kerülje a modelladatok közvetlen átadását a komponenseknek attribútumokon keresztül
A key
tulajdonság az adatmodellben olyan módon is megjelenhet, ami ütközik a Mithril.js key logikájával, és maga a modell is lehet egy módosítható példány egy olyan metódussal, ami azonos nevű egy életciklus hook-kal, mint például az onupdate
vagy az onremove
. Például egy modell használhat egy key
tulajdonságot egy testreszabható színkulcs ábrázolására. Amikor ez megváltozik, az ahhoz vezethet, hogy a komponensek helytelen adatokat kapnak, váratlanul pozíciót váltanak, vagy más váratlan, nem kívánt viselkedést mutatnak. Ehelyett add át azt attribútumként, hogy a Mithril.js ne értelmezze félre (és hogy később is módosíthasd vagy meghívhasd a prototípus metódusait):
// Adatmodell
var users = [
{ id: 1, name: 'John', key: 'red' },
{ id: 2, name: 'Mary', key: 'blue' },
];
// Később...
users[0].key = 'yellow';
// KERÜLD
users.map(function (user) {
// John komponense törlődik és újra létrejön
return m(UserComponent, user);
});
// ELŐNYBEN RÉSZESÍTD
users.map(function (user) {
// A key kifejezetten ki van emelve: az adatmodell saját tulajdonságot kap
return m(UserComponent, { key: user.id, model: user });
});
Kerülje az utasításokat a view metódusokban
A JavaScript utasítások a view metódusokban gyakran megkövetelik a HTML fa természetes, beágyazott struktúrájának megváltoztatását, ami a kódot bőbeszédűbbé és kevésbé olvashatóvá teszi.
// KERÜLD
var BadListComponent = {
view: function (vnode) {
var list = [];
for (var i = 0; i < vnode.attrs.items.length; i++) {
list.push(m('li', vnode.attrs.items[i]));
}
return m('ul', list);
},
};
Ehelyett részesítsd előnyben a JavaScript kifejezéseket, mint például a ternáris operátort a feltételes rendereléshez és a tömb metódusokat a listához hasonló struktúrákhoz.
// ELŐNYBEN RÉSZESÍTD
var BetterListComponent = {
view: function (vnode) {
return m(
'ul',
vnode.attrs.items.map(function (item) {
return m('li', item);
})
);
},
};