Węzły Virtual DOM
Czym jest Virtual DOM
Virtual DOM to struktura danych JavaScript, która opisuje drzewo DOM. Składa się z zagnieżdżonych węzłów Virtual DOM, zwanych również vnode.
Podczas pierwszego renderowania drzewa Virtual DOM jest ono używane jako wzorzec do utworzenia drzewa DOM, które odpowiada jego strukturze.
Może się wydawać, że tak częste odtwarzanie vnode jest nieefektywne, ale nowoczesne silniki JavaScript potrafią tworzyć setki tysięcy obiektów w mniej niż milisekundę. Z drugiej strony, modyfikowanie DOM jest o kilka rzędów wielkości wolniejsze niż tworzenie vnode.
Dlatego Mithril.js używa zaawansowanego i zoptymalizowanego algorytmu porównywania Virtual DOM, minimalizując liczbę aktualizacji DOM. Mithril.js generuje również starannie przygotowane struktury danych vnode, które są kompilowane przez silniki JavaScript, aby zapewnić wydajność dostępu do danych porównywalną z natywną. Ponadto Mithril.js intensywnie optymalizuje również funkcję tworzącą vnode.
Mithril.js dokłada wszelkich starań, aby wspierać model renderowania, który odtwarza całe drzewo Virtual DOM przy każdym renderowaniu. Ma to na celu zapewnienie deklaratywnego API trybu natychmiastowego, stylu renderowania, który radykalnie ułatwia zarządzanie złożonością interfejsu użytkownika.
Aby zilustrować znaczenie trybu natychmiastowego, rozważmy API DOM i HTML. API DOM jest imperatywnym API trybu zachowanego i wymaga: 1. wypisywania dokładnych instrukcji w celu proceduralnego złożenia drzewa DOM oraz 2. wypisywania innych instrukcji w celu aktualizacji tego drzewa. Imperatywny charakter API DOM oznacza, że masz wiele możliwości mikrooptymalizacji kodu, ale wiąże się to również z większym ryzykiem popełnienia błędów i utrudnia zrozumienie kodu.
Z kolei HTML jest bliższy systemowi renderowania w trybie natychmiastowym. Za pomocą HTML możesz zapisać drzewo DOM w znacznie bardziej naturalny i czytelny sposób, nie martwiąc się o zapomnienie dołączenia elementu potomnego do elementu nadrzędnego, napotkanie przepełnienia stosu podczas renderowania bardzo głębokich drzew itp.
Virtual DOM idzie o krok dalej niż HTML, pozwalając na pisanie dynamicznych drzew DOM bez konieczności ręcznego pisania wielu zestawów wywołań API DOM, aby efektywnie synchronizować interfejs użytkownika z dowolnymi zmianami danych.
Podstawy
Węzły Virtual DOM, czyli vnode, to obiekty JavaScript, które reprezentują elementy DOM (lub fragmenty DOM). Silnik Virtual DOM Mithril.js przetwarza drzewo vnode w celu wygenerowania drzewa DOM.
Vnode są tworzone za pomocą funkcji m()
hyperscript:
m('div', { id: 'test' }, 'hello');
Hyperscript może również używać komponentów:
// zdefiniuj komponent
var ExampleComponent = {
view: function (vnode) {
return m('div', vnode.attrs, ['Hello ', vnode.children]);
},
};
// użyj go
m(ExampleComponent, { style: 'color:red;' }, 'world');
// odpowiednik HTML:
// <div style="color:red;">Hello world</div>
Struktura
Węzły Virtual DOM, czyli vnode, to obiekty JavaScript, które reprezentują element (lub fragment DOM) i mają następujące właściwości:
Właściwość | Typ | Opis |
---|---|---|
tag | String|Object | Nazwa węzła (nodeName ) elementu DOM. Może to być również ciąg [ jeśli vnode reprezentuje fragment, # jeśli jest to węzeł tekstowy lub < jeśli jest to zaufany węzeł HTML. Dodatkowo, może to być komponent. |
key | String? | Wartość używana do mapowania elementu DOM na odpowiedni element w tablicy danych. |
attrs | Object? | Obiekt zawierający atrybuty DOM, obsługę zdarzeń, właściwości i metody cyklu życia. |
children | (Array|String|Number|Boolean)? | W większości typów vnode właściwość children jest tablicą vnode. Dla węzłów tekstowych i zaufanych węzłów HTML właściwość children jest ciągiem, liczbą lub wartością boolowską. |
text | (String|Number|Boolean)? | Używane zamiast children , jeśli vnode zawiera węzeł tekstowy jako jedyny element potomny. Robi się to ze względów wydajnościowych. Komponentowe vnode nigdy nie używają właściwości text , nawet jeśli mają węzeł tekstowy jako jedyny element potomny. |
dom | Element? | Wskazuje element DOM, który odpowiada vnode. Ta właściwość jest undefined w metodzie cyklu życia oninit . W fragmentach i zaufanych węzłach HTML, dom wskazuje na pierwszy element w zakresie. |
domSize | Number? | Ustawiane tylko w fragmentach i zaufanych węzłach HTML i jest undefined we wszystkich innych typach vnode. Definiuje liczbę elementów DOM, które reprezentuje vnode (zaczynając od elementu, do którego odwołuje się właściwość dom ). |
state | Object? | Obiekt, który jest zachowywany między kolejnymi renderowaniami. Jest on dostarczany przez rdzeń w razie potrzeby. W vnode komponentów POJO, state dziedziczy prototypowo z obiektu/klasy komponentu. W klasowych vnode komponentów jest to instancja klasy. W komponentach funkcji domknięcia jest to obiekt zwracany przez funkcję domknięcia. |
events | Object? | Obiekt, który jest zachowywany między kolejnymi renderowaniami i który przechowuje obsługę zdarzeń, aby można je było usunąć za pomocą API DOM. Właściwość events jest undefined , jeśli nie zdefiniowano żadnych obsługi zdarzeń. Ta właściwość jest używana tylko wewnętrznie przez Mithril.js, nie używaj jej ani nie modyfikuj. |
instance | Object? | Dla komponentów, miejsce przechowywania wartości zwracanej przez view . Ta właściwość jest używana tylko wewnętrznie przez Mithril.js, nie używaj jej ani nie modyfikuj. |
Typy Vnode
Właściwość tag
vnode określa jego typ. Istnieje pięć typów vnode:
Typ Vnode | Przykład | Opis |
---|---|---|
Element | {tag: "div"} | Reprezentuje element DOM. |
Fragment | {tag: "[", children: []} | Reprezentuje listę elementów DOM, których element nadrzędny DOM może również zawierać inne elementy, które nie znajdują się we fragmencie. Podczas korzystania z funkcji pomocniczej m() , vnode fragmentu można tworzyć tylko przez zagnieżdżanie tablic w parametrze children funkcji m() . Wywołanie m("[") nie tworzy prawidłowego vnode. |
Tekst | {tag: "#", children: ""} | Reprezentuje węzeł tekstowy DOM. |
Zaufany HTML | {tag: "<", children: "<br>"} | Reprezentuje listę elementów DOM z ciągu HTML. |
Komponent | {tag: ExampleComponent} | Jeśli tag jest obiektem JavaScript z metodą view , vnode reprezentuje DOM wygenerowany przez renderowanie komponentu. |
Wszystko w drzewie Virtual DOM jest vnode, w tym tekst. Funkcja m()
automatycznie normalizuje swój argument children
i zamienia ciągi w tekstowe vnode, a zagnieżdżone tablice w vnode fragmentu.
Tylko nazwy tagów elementów i komponenty mogą być pierwszym argumentem funkcji m()
. Innymi słowy, [
, #
i <
nie są prawidłowymi argumentami selector
dla m()
. Zaufane vnode HTML można tworzyć za pomocą m.trust()
Klasa monomorficzna
Moduł mithril/render/vnode
jest używany przez Mithril.js do generowania wszystkich vnode. Zapewnia to, że nowoczesne silniki JavaScript mogą optymalizować porównywanie Virtual DOM, zawsze kompilując vnode do tej samej ukrytej klasy.
Podczas tworzenia bibliotek, które emitują vnode, należy używać tego modułu zamiast tworzyć vnode bezpośrednio jako obiekty JavaScript, aby zapewnić wysoki poziom wydajności renderowania.
Unikaj antywzorców
Unikaj ponownego używania vnode
Vnode mają reprezentować stan DOM w określonym momencie. Silnik renderowania Mithril.js zakłada, że ponownie użyty vnode jest niezmieniony, więc modyfikowanie vnode, który był używany w poprzednim renderowaniu, spowoduje niezdefiniowane zachowanie.
Możliwe jest ponowne użycie vnode w miejscu, aby zapobiec porównaniu, ale preferowane jest użycie onbeforeupdate
.
Unikaj przekazywania danych modelu bezpośrednio do komponentów za pośrednictwem atrybutów
Właściwość key
może pojawić się w twoim modelu danych w sposób, który koliduje z logiką kluczy Mithril.js, a twój model może sam w sobie być zmienną instancją z metodą, która ma nazwę wspólną z hakiem cyklu życia, takim jak onupdate
lub onremove
. Na przykład model może używać właściwości key
do reprezentowania konfigurowalnego klucza koloru. Kiedy to się zmieni, może to prowadzić do tego, że komponenty będą otrzymywać błędne dane, zmieniać pozycje w nieoczekiwany sposób lub inne nieoczekiwane, niepożądane zachowanie. Zamiast tego przekaż go jako atrybut, aby Mithril.js nie zinterpretował go błędnie (i abyś nadal mógł potencjalnie go mutować lub wywoływać metody prototypu na nim później):
// Model danych
var users = [
{ id: 1, name: 'John', key: 'red' },
{ id: 2, name: 'Mary', key: 'blue' },
];
// Później...
users[0].key = 'yellow';
// UNIKAJ
users.map(function (user) {
// Komponent dla Johna zostanie zniszczony i odtworzony
return m(UserComponent, user);
});
// PREFERUJ
users.map(function (user) {
// Klucz jest specjalnie wyodrębniony: model danych otrzymuje własną właściwość
return m(UserComponent, { key: user.id, model: user });
});
Unikaj instrukcji w metodach widoku
Instrukcje JavaScript w metodach widoku często wymagają zmiany naturalnie zagnieżdżonej struktury drzewa HTML, co sprawia, że kod jest bardziej rozwlekły i mniej czytelny.
// UNIKAJ
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);
},
};
Zamiast tego preferuj używanie wyrażeń JavaScript, takich jak operator trójargumentowy do warunkowego renderowania i metody Array dla struktur listowych.
// PREFERUJ
var BetterListComponent = {
view: function (vnode) {
return m(
'ul',
vnode.attrs.items.map(function (item) {
return m('li', item);
})
);
},
};