Migration depuis la v0.2.x
Les versions v1.x et v2.x sont largement compatibles avec l'API de la v0.2.x, mais il existe quelques changements importants. La migration vers la v2.x est presque identique à celle vers la v1.x, les notes ci-dessous s'appliquent donc principalement aux deux.
Si vous migrez, envisagez d'utiliser l'outil mithril-codemods pour automatiser les migrations les plus simples.
m.prop
supprimé
Dans la v2.x, m.prop()
a été transformé en une micro-bibliothèque de flux plus performante, mais elle ne fait plus partie du cœur de Mithril. Vous pouvez consulter la documentation du module Streams optionnel dans la documentation.
v0.2.x
var m = require('mithril');
var num = m.prop(1);
v2.x
var m = require('mithril');
var prop = require('mithril/stream');
var num = prop(1);
var doubled = num.map(function (n) {
return n * 2;
});
m.component
supprimé
Dans la v0.2.x, les composants pouvaient être créés en utilisant m(Component)
ou m.component(Component)
. La v2.x ne supporte que m(Component)
.
v0.2.x
// Ces instructions sont équivalentes
m.component(Component);
m(Component);
v2.x
m(Component);
m.withAttr
supprimé
Dans la v0.2.x, les gestionnaires d'événements pouvaient utiliser oninput: m.withAttr("value", func)
et similaires. Dans la v2.x, il suffit de lire la valeur directement à partir de la propriété target
de l'objet event
. Cela fonctionnait bien avec m.prop
, mais celui-ci ayant été supprimé au profit d'une solution hors du cœur et la v1.x n'ayant pas vu d'utilisation idiomatique aussi large des flux, m.withAttr
a perdu la plupart de son utilité.
v0.2.x
var value = m.prop('');
// Dans votre vue
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', value),
});
v2.x
var value = '';
// Dans votre vue
m('input[type=text]', {
value: value,
oninput: function (ev) {
value = ev.target.value;
},
});
m.version
supprimé
Cette propriété était généralement peu utile, et vous pouvez toujours l'ajouter vous-même. Il est préférable d'utiliser la détection de fonctionnalités pour déterminer quelles fonctionnalités sont disponibles, et l'API v2.x est conçue pour mieux le permettre.
Fonction config
Dans la v0.2.x, Mithril.js fournissait une seule méthode de cycle de vie, config
. La v2.x offre un contrôle beaucoup plus précis sur le cycle de vie d'un vnode (nœud virtuel).
v0.2.x
m('div', {
config: function (element, isInitialized) {
// S'exécute à chaque rafraîchissement
// isInitialized est un booléen indiquant si le nœud a été ajouté au DOM
},
});
v2.x
Plus de documentation sur ces nouvelles méthodes est disponible dans lifecycle-methods.md.
m('div', {
// Appelé avant la création du nœud DOM
oninit: function (vnode) {
/*...*/
},
// Appelé après la création du nœud DOM
oncreate: function (vnode) {
/*...*/
},
// Appelé avant la mise à jour du nœud, renvoie false pour annuler
onbeforeupdate: function (vnode, old) {
/*...*/
},
// Appelé après la mise à jour du nœud
onupdate: function (vnode) {
/*...*/
},
// Appelé avant la suppression du nœud, renvoie une Promise qui se résout lorsque
// le nœud est prêt à être supprimé du DOM
onbeforeremove: function (vnode) {
/*...*/
},
// Appelé après la suppression du nœud, mais après que onbeforeremove appelle done()
onremove: function (vnode) {
/*...*/
},
});
Si disponible, l'élément DOM du vnode est accessible via vnode.dom
.
Changements dans le comportement de rafraîchissement
Le moteur de rendu de Mithril.js fonctionne toujours sur la base de rafraîchissements globaux semi-automatisés, mais certaines API et certains comportements sont différents.
Plus de verrouillage de rafraîchissement
Dans la v0.2.x, Mithril.js autorisait les "verrouillages de rafraîchissement" qui empêchaient temporairement la logique de dessin bloquée : par défaut, m.request
verrouillait la boucle de dessin lors de l'exécution et la déverrouillait lorsque toutes les requêtes en attente étaient résolues - le même comportement pouvait être invoqué manuellement à l'aide de m.startComputation()
et m.endComputation()
. Ces dernières API et le comportement associé ont été supprimés dans la v2.x sans remplacement. Le verrouillage de rafraîchissement peut entraîner des interfaces utilisateur boguées : il ne faut pas que les préoccupations d'une partie de l'application empêchent d'autres parties de la vue de se mettre à jour pour refléter les changements.
Annulation du rafraîchissement à partir des gestionnaires d'événements
m.mount()
et m.route()
rafraîchissent toujours automatiquement après l'exécution d'un gestionnaire d'événements DOM. Vous pouvez maintenant annuler ces rafraîchissements dans vos gestionnaires d'événements en définissant la propriété redraw
de l'objet d'événement à false
.
v0.2.x
m('div', {
onclick: function (e) {
m.redraw.strategy('none');
},
});
v2.x
m('div', {
onclick: function (e) {
e.redraw = false;
},
});
Redessin synchrone modifié
Dans la v0.2.x, il était possible de forcer Mithril.js à rafraîchir immédiatement en passant une valeur évaluée à true
à m.redraw()
. Dans la v2.x, cette fonctionnalité a été divisée en deux méthodes différentes pour plus de clarté.
v0.2.x
m.redraw(true); // redessine immédiatement et synchroniquement
v2.x
m.redraw(); // planifie un rafraîchissement lors du prochain cycle de requestAnimationFrame
m.redraw.sync(); // invoque un rafraîchissement immédiatement et attend qu'il se termine
m.startComputation
/m.endComputation
supprimés
Ces fonctions sont considérées comme des anti-modèles en raison de leurs cas limites problématiques. Elles ont donc été supprimées sans remplacement dans la v2.x.
Fonction controller
du composant
Dans la v2.x, la propriété controller
n'existe plus dans les composants ; utilisez oninit
à la place.
v0.2.x
m.mount(document.body, {
controller: function () {
var ctrl = this;
ctrl.fooga = 1;
},
view: function (ctrl) {
return m('p', ctrl.fooga);
},
});
v2.x
m.mount(document.body, {
oninit: function (vnode) {
vnode.state.fooga = 1;
},
view: function (vnode) {
return m('p', vnode.state.fooga);
},
});
// OU
m.mount(document.body, {
// `this` est lié à `vnode.state` par défaut
oninit: function (vnode) {
this.fooga = 1;
},
view: function (vnode) {
return m('p', this.fooga);
},
});
Arguments du composant
Dans la v2.x, les arguments d'un composant doivent être un objet. Les valeurs simples telles que String
, Number
ou Boolean
seront traitées comme des enfants de texte. Les arguments sont accessibles dans le composant en les lisant à partir de l'objet vnode.attrs
.
v0.2.x
var Component = {
controller: function (options) {
// options.fooga === 1
},
view: function (ctrl, options) {
// options.fooga === 1
},
};
m('div', m.component(Component, { fooga: 1 }));
v2.x
var Component = {
oninit: function (vnode) {
// vnode.attrs.fooga === 1
},
view: function (vnode) {
// vnode.attrs.fooga === 1
},
};
m('div', m(Component, { fooga: 1 }));
Enfants vnode du composant
Dans la v0.2.x, les enfants vnode du composant n'étaient pas normalisés et étaient simplement passés comme arguments supplémentaires, et ils n'étaient pas non plus aplatis. (En interne, il s'agissait simplement de renvoyer un composant partiellement appliqué dont la différence était calculée en fonction du composant partiellement appliqué.) Dans la v2.x, les enfants vnode du composant sont transmis via vnode.children
sous forme de tableau résolu d'enfants, mais comme dans la v0.2.x, les enfants individuels eux-mêmes ne sont pas normalisés, ni le tableau d'enfants aplati.
v0.2.x
var Component = {
controller: function (value, renderProp) {
// value === "value"
// typeof renderProp === "function"
},
view: function (ctrl, value, renderProp) {
// value === "value"
// typeof renderProp === "function"
},
};
m(
'div',
m.component(Component, 'value', function (key) {
return 'child';
})
);
v2.x
var Component = {
oninit: function (vnode) {
// vnode.children[0] === "value"
// typeof vnode.children[1] === "function"
},
view: function (vnode) {
// vnode.children[0] === "value"
// typeof vnode.children[1] === "function"
},
};
m(
'div',
m(Component, 'value', function (key) {
return 'child';
})
);
Enfants vnode DOM
Dans la v0.2.x, les enfants des nœuds DOM étaient représentés littéralement, sans normalisation, à part l'utilisation directe des enfants si un seul enfant de tableau est présent. Il renvoyait une structure plus proche de celle-ci, avec les chaînes représentées littéralement.
m("div", "value", ["nested"])
// Devient :
{
tag: "div",
attrs: {},
children: [
"value",
["nested"],
]
}
Dans la v2.x, les enfants des vnodes DOM sont normalisés en objets ayant une structure cohérente.
m("div", "value", ["nested"])
// Devient à peu près :
{
tag: "div",
attrs: null,
children: [
{tag: "#", children: "value"},
{tag: "[", children: [
{tag: "#", children: "nested"},
]},
]
}
Si un seul enfant de texte est présent sur un vnode DOM, la propriété text
est définie sur cette valeur.
m("div", "value")
// Devient à peu près :
{
tag: "div",
attrs: null,
text: "value",
children: undefined,
}
Consultez la documentation vnode pour plus de détails sur la structure vnode v2.x et la façon dont les choses sont normalisées.
La plupart des propriétés vnode v2.x ici sont omises par souci de concision.
Clés (Keys)
Dans la v0.2.x, vous pouviez mélanger librement les vnodes avec et sans clé (keyed et unkeyed).
Dans la v2.x, les listes d'enfants des fragments et des éléments doivent être soit toutes avec clé, soit toutes sans clé. Les emplacements vides sont également considérés comme sans clé aux fins de cette vérification - ils ne sont plus ignorés.
Si vous devez contourner ce problème, utilisez l'idiome d'un fragment contenant un seul vnode, comme [m("div", {key: whatever})]
.
Paramètres view()
Dans la v0.2.x, les fonctions de vue recevaient une référence à l'instance controller
et (éventuellement) toutes les options passées au composant. Dans la v2.x, elles reçoivent uniquement le vnode
, exactement comme la fonction controller
.
v0.2.x
m.mount(document.body, {
controller: function () {},
view: function (ctrl, options) {
// ...
},
});
v2.x
m.mount(document.body, {
oninit: function (vnode) {
// ...
},
view: function (vnode) {
// Utilisez vnode.state au lieu de ctrl
// Utilisez vnode.attrs au lieu d'options
},
});
Passage de composants à m()
Dans la v0.2.x, vous pouviez passer des composants comme deuxième argument de m()
sans nécessiter de wrapping. Pour plus de cohérence dans la v2.x, ils doivent toujours être enveloppés dans un appel à m()
.
v0.2.x
m('div', Component);
v2.x
m('div', m(Component));
Passage de vnodes à m.mount()
et m.route()
Dans la v0.2.x, m.mount(element, component)
tolérait les vnodes comme deuxièmes arguments au lieu des composants (même si ce n'était pas documenté). De même, m.route(element, defaultRoute, routes)
acceptait les vnodes comme valeurs dans l'objet routes
.
Dans la v2.x, des composants sont requis à la place dans les deux cas.
v0.2.x
m.mount(element, m('i', 'bonjour'));
m.mount(element, m(Component, attrs));
m.route(element, '/', {
'/': m('b', 'bye'),
});
v2.x
m.mount(element, {
view: function () {
return m('i', 'bonjour');
},
});
m.mount(element, {
view: function () {
return m(Component, attrs);
},
});
m.route(element, '/', {
'/': {
view: function () {
return m('b', 'bye');
},
},
});
m.route.mode
Dans la version v0.2.x, le mode de routage pouvait être défini en assignant une chaîne de caractères ("pathname"
, "hash"
ou "search"
) à m.route.mode
. Dans la v1.x, cette approche est remplacée par m.route.prefix = prefix
, où prefix
peut être n'importe quel préfixe. Si prefix
commence par #
, le routage fonctionne en mode "hash" ; si c'est ?
, en mode "search" ; et tout autre caractère (ou une chaîne vide) active le mode "pathname". Les combinaisons sont également prises en charge, comme m.route.prefix = "/path/#!"
ou ?#
.
La valeur par défaut a également été modifiée pour utiliser un préfixe #!
(hashbang) au lieu de simplement #
. Par conséquent, si vous utilisiez le comportement par défaut et que vous souhaitez conserver vos URL existantes, spécifiez m.route.prefix = "#"
avant d'initialiser les routes.
v0.2.x
m.route.mode = 'hash';
m.route.mode = 'pathname';
m.route.mode = 'search';
v2.x
// Équivalents directs :
m.route.prefix = '#';
m.route.prefix = '';
m.route.prefix = '?';
m.route()
et les liens
La gestion des liens routables utilise désormais un composant intégré spécifique au lieu d'un attribut. Si vous utilisiez cette fonctionnalité sur des balises <button>
ou similaires, vous pouvez spécifier le nom de la balise en utilisant l'attribut selector: "button"
.
v0.2.x
// Lorsque vous cliquez sur ce lien, il chargera la route "/path" au lieu de rediriger
m('a', {
href: '/path',
config: m.route,
});
v2.x
// Lorsque vous cliquez sur ce lien, il chargera la route "/path" au lieu de rediriger
m(m.route.Link, {
href: '/path',
});
Modèles d'URL
Dans la version v1.x, il existait trois syntaxes de modèle de chemin distinctes qui, bien que similaires, avaient deux syntaxes conçues séparément et trois implémentations différentes. La définition était assez ad hoc et les paramètres n'étaient généralement pas échappés. Désormais, tout est soit encodé si c'est :key
, soit brut si c'est :key...
. Si l'encodage est inattendu, utilisez :path...
. C'est aussi simple que cela.
Voici comment cela affecte chaque méthode :
URLs de m.request
Dans la version v2.x, les segments de chemin sont automatiquement échappés lors de l'interpolation et leurs valeurs sont lues à partir de params
. Dans la version v0.2.x, m.request({url: "/user/:name/photos/:id", data: {name: "a/b", id: "c/d"}})
envoyait sa requête avec l'URL définie sur /user/a%2Fb/photos/c/d
. Dans la version v2.x, l'équivalent m.request({url: "/user/:name/photos/:id", params: {name: "a/b", id: "c/d"}})
enverra sa requête à /user/a%2Fb/photos/c%2Fd
. Si vous souhaitez délibérément interpoler une clé non échappée, utilisez plutôt :key...
.
Les interpolations dans les chaînes de requête en ligne, comme dans /api/search?q=:query
, ne sont pas effectuées dans la version v2.x. Passez-les plutôt via params
avec les noms de clé appropriés, sans les spécifier dans la chaîne de requête.
Notez que cela s'applique également à m.jsonp
. Lors de la migration de m.request
+ dataType: "jsonp"
vers m.jsonp
, vous devez également en tenir compte.
Chemins m.route(route, params, shouldReplaceHistoryEntry)
Ils permettent désormais les interpolations, et ils fonctionnent de manière identique à celles de m.request
.
Motifs de route m.route
Les clés de chemin de la forme :key...
renvoient leur URL décodée dans la version v1.x, mais renvoient l'URL brute dans la version v2.x.
Auparavant, des éléments tels que :key.md
étaient acceptés par erreur, la valeur du paramètre résultant étant définie sur keymd: "..."
. Ce n'est plus le cas : .md
fait désormais partie du motif, et non du nom.
Lecture/écriture de la route actuelle
Dans la version v0.2.x, toute interaction avec la route actuelle se faisait via m.route()
. Dans la version v2.x, cette fonctionnalité a été divisée en deux fonctions.
v0.2.x
// Obtenir la route actuelle
m.route();
// Définir une nouvelle route
m.route('/other/route');
v2.x
// Obtenir la route actuelle
m.route.get();
// Définir une nouvelle route
m.route.set('/other/route');
Accès aux paramètres de route
Dans la version v0.2.x, la lecture des paramètres de route était entièrement gérée via m.route.param()
. Cette API reste disponible dans la version v2.x. De plus, tous les paramètres de route sont transmis en tant que propriétés dans l'objet attrs
du vnode.
v0.2.x
m.route(document.body, '/booga', {
'/:attr': {
controller: function () {
m.route.param('attr'); // "booga"
},
view: function () {
m.route.param('attr'); // "booga"
},
},
});
v2.x
m.route(document.body, '/booga', {
'/:attr': {
oninit: function (vnode) {
vnode.attrs.attr; // "booga"
m.route.param('attr'); // "booga"
},
view: function (vnode) {
vnode.attrs.attr; // "booga"
m.route.param('attr'); // "booga"
},
},
});
Construction/analyse des chaînes de requête
La version v0.2.x utilisait des méthodes dépendant de m.route
, m.route.buildQueryString()
et m.route.parseQueryString()
. Dans la version v2.x, ces méthodes ont été divisées et déplacées vers la racine m
.
v0.2.x
var qs = m.route.buildQueryString({ a: 1 });
var obj = m.route.parseQueryString('a=1');
v2.x
var qs = m.buildQueryString({ a: 1 });
var obj = m.parseQueryString('a=1');
De plus, dans la version v2.x, {key: undefined}
est sérialisé en tant que key=undefined
par m.buildQueryString
et les méthodes qui l'utilisent, comme m.request
. Dans la version v0.2.x, la clé était omise, et cela était reporté à m.request
. Si vous vous fiez à ce comportement auparavant, modifiez votre code pour omettre complètement les clés de l'objet. Il peut être utile d'utiliser un utilitaire simple pour supprimer toutes les clés d'un objet dont les valeurs sont undefined
si vous ne pouvez pas le faire facilement et que vous devez conserver le comportement de la version v0.2.x.
// Appeler chaque fois que vous devez omettre les paramètres `undefined` d'un objet.
function omitUndefineds(object) {
var result = {};
for (var key in object) {
if ({}.hasOwnProperty.call(object, key)) {
var value = object[key];
if (Array.isArray(value)) {
result[key] = value.map(omitUndefineds);
} else if (value != null && typeof value === 'object') {
result[key] = omitUndefineds(value);
} else if (value !== undefined) {
result[key] = value;
}
}
}
return result;
}
Prévenir le démontage
Il n'est plus possible d'empêcher le démontage via e.preventDefault()
de onunload
. Au lieu de cela, vous devez appeler explicitement m.route.set
lorsque les conditions prévues sont remplies.
v0.2.x
var Component = {
controller: function () {
this.onunload = function (e) {
if (condition) e.preventDefault();
};
},
view: function () {
return m('a[href=/]', { config: m.route });
},
};
v2.x
var Component = {
view: function () {
return m('a', {
onclick: function () {
if (!condition) m.route.set('/');
},
});
},
};
Exécuter du code lors de la suppression du composant
Les composants n'appellent plus this.onunload
lorsqu'ils sont supprimés. Ils utilisent désormais le hook du cycle de vie standardisé onremove
.
v0.2.x
var Component = {
controller: function () {
this.onunload = function (e) {
// ...
};
},
view: function () {
// ...
},
};
v2.x
var Component = {
onremove: function() {
// ...
}
view: function() {
// ...
}
}
m.request
Les Promises renvoyées par m.request ne sont plus des accesseurs-mutateurs m.prop
. De plus, initialValue
, unwrapSuccess
et unwrapError
ne sont plus des options prises en charge.
De plus, les requêtes n'ont plus la sémantique m.startComputation
/m.endComputation
. Au lieu de cela, les redessins sont toujours déclenchés lorsqu'une chaîne de Promise de requête se termine (sauf si background: true
est défini).
Le paramètre data
a maintenant été divisé en params
(paramètres de requête interpolés dans l'URL et ajoutés à la requête) et body
(le corps à envoyer dans le XHR sous-jacent).
Dans la version v0.2.x, vous utilisiez dataType: "jsonp"
pour lancer une requête JSONP. Dans la version v2.x, vous utilisez maintenant m.jsonp
, qui possède principalement la même API que m.request
sans les parties liées à XHR.
v0.2.x
var data = m.request({
method: 'GET',
url: 'https://api.github.com/',
initialValue: [],
});
setTimeout(function () {
console.log(data());
}, 1000);
m.request({
method: 'POST',
url: 'https://api.github.com/',
data: someJson,
});
v2.x
var data = [];
m.request({
method: 'GET',
url: 'https://api.github.com/',
}).then(function (responseBody) {
data = responseBody;
});
setTimeout(function () {
console.log(data); // note : pas un accesseur-mutateur
}, 1000);
m.request({
method: 'POST',
url: 'https://api.github.com/',
body: someJson,
});
// OU
var data = [];
m.request('https://api.github.com/').then(function (responseBody) {
data = responseBody;
});
setTimeout(function () {
console.log(data); // note : pas un accesseur-mutateur
}, 1000);
m.request('https://api.github.com/', {
method: 'POST',
body: someJson,
});
De plus, si l'option extract
est transmise à m.request
, la valeur de retour de la fonction fournie sera utilisée directement pour résoudre la Promise de requête, et le callback deserialize
est ignoré.
En-têtes m.request
Dans la version v0.2.x, Mithril.js ne définissait aucun en-tête sur les requêtes par défaut. Désormais, il définit jusqu'à deux en-têtes :
Content-Type: application/json; charset=utf-8
pour les requêtes avec des corps JSON qui sont!= null
Accept: application/json, text/*
pour les requêtes attendant des réponses JSON
Le premier de ces en-têtes, Content-Type
, déclenchera une prélecture CORS car il n'est pas un en-tête de requête CORS-safelisted en raison du type de contenu spécifié, et cela pourrait introduire de nouvelles erreurs en fonction de la façon dont CORS est configuré sur votre serveur. Si vous rencontrez des problèmes, vous devrez peut-être remplacer l'en-tête concerné en transmettant headers: {"Content-Type": "text/plain"}
. (L'en-tête Accept
ne déclenche rien, vous n'avez donc pas besoin de le remplacer.)
Les seuls types de contenu que la spécification Fetch autorise pour éviter les vérifications de prélecture CORS sont application/x-www-form-urlencoded
, multipart/form-data
et text/plain
. Elle n'autorise rien d'autre, et interdit intentionnellement JSON.
m.deferred
supprimé
La version v0.2.x utilisait son propre objet de contrat asynchrone personnalisé, exposé sous le nom de m.deferred
, qui était utilisé comme base pour m.request
. La version v2.x utilise plutôt des Promises et implémente un polyfill dans les environnements non pris en charge. Dans les situations où vous auriez utilisé m.deferred
, il est préférable d'utiliser des Promises.
v0.2.x
var greetAsync = function () {
var deferred = m.deferred();
setTimeout(function () {
deferred.resolve('hello');
}, 1000);
return deferred.promise;
};
greetAsync()
.then(function (value) {
return value + ' world';
})
.then(function (value) {
console.log(value);
}); //enregistre "hello world" après 1 seconde
v2.x
var greetAsync = function () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve('hello');
}, 1000);
});
};
greetAsync()
.then(function (value) {
return value + ' world';
})
.then(function (value) {
console.log(value);
}); //enregistre "hello world" après 1 seconde
m.sync
supprimé
Étant donné que la version v2.x utilise des Promises conformes aux normes, m.sync
est redondant. Utilisez plutôt Promise.all
.
v0.2.x
m.sync([
m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
m.request({
method: 'GET',
url: 'https://api.github.com/users/dead-claudia',
}),
]).then(function (users) {
console.log('Contributors:', users[0].name, 'and', users[1].name);
});
v2.x
Promise.all([
m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
m.request({
method: 'GET',
url: 'https://api.github.com/users/dead-claudia',
}),
]).then(function (users) {
console.log('Contributors:', users[0].name, 'and', users[1].name);
});
Espace de noms xlink
requis
Dans la version v0.2.x, l'espace de noms xlink
était le seul espace de noms d'attribut pris en charge, et il était pris en charge via un comportement de cas spécial. Désormais, l'analyse des espaces de noms est entièrement prise en charge et les attributs avec espace de noms doivent déclarer explicitement leur espace de noms.
v0.2.x
m(
'svg',
// l'attribut `href` est automatiquement dans l'espace de noms
m("image[href='image.gif']")
);
v2.x
m(
'svg',
// Espace de noms spécifié par l'utilisateur sur l'attribut `href`
m("image[xlink:href='image.gif']")
);
Tableaux imbriqués dans les vues
Les tableaux représentent désormais des fragments, qui sont structurellement significatifs dans le DOM virtuel de la version v2.x. Alors que les tableaux imbriqués dans la version v0.2.x seraient aplatis en une liste continue de nœuds virtuels aux fins de la différenciation, la version v2.x préserve la structure du tableau : les enfants d'un tableau donné ne sont pas considérés comme des frères et sœurs de ceux des tableaux adjacents.
Comparaisons d'égalité vnode
Si un vnode est strictement égal au vnode occupant sa place lors du dernier dessin, la version v2.x ignorera cette partie de l'arborescence sans vérifier les mutations ni déclencher de méthodes de cycle de vie dans la sous-arborescence. La documentation du composant contient plus de détails sur ce problème.