Migration depuis la version 1.x
La version 2.x est largement compatible avec l'API de la version 1.x, mais certains changements importants sont à noter.
Affectation à vnode.state
Dans la version 1.x, il était possible de manipuler vnode.state
et d'y affecter n'importe quelle valeur. Dans la version 2.x, une tentative de modification directe de vnode.state
générera une erreur. La migration dépendra de votre code, mais dans la plupart des cas, il suffit de remplacer les références à vnode.state
par vnode.state.foo
, en choisissant un nom approprié pour foo
(par exemple, count
si cela représente la valeur d'un compteur).
version 1.x
var Counter = {
oninit: function (vnode) {
vnode.state = 0;
},
view: function (vnode) {
return m('.counter', [
m(
'button',
{
onclick: function () {
vnode.state--;
},
},
'-'
),
vnode.state,
m(
'button',
{
onclick: function () {
vnode.state++;
},
},
'+'
),
]);
},
};
version 2.x
var Counter = {
oninit: function (vnode) {
vnode.state.count = 0;
},
view: function (vnode) {
return m('.counter', [
m(
'button',
{
onclick: function () {
vnode.state.count--;
},
},
'-'
),
vnode.state.count,
m(
'button',
{
onclick: function () {
vnode.state.count++;
},
},
'+'
),
]);
},
};
Lors de la première publication de la version 1.0, les composants de classe et de fermeture n'existaient pas. Le composant récupérait simplement les données nécessaires depuis vnode.tag
. Cette particularité d'implémentation permettait la manipulation directe de vnode.state
, et certains développeurs ont commencé à en dépendre. Cela était également implicite dans certains passages de la documentation. Désormais, l'approche est différente, ce qui simplifie la gestion d'un point de vue de l'implémentation, car il n'y a qu'une seule référence à l'état (state), et non deux.
Modifications des ancres de route
Dans la version 1.x, on utilisait oncreate: m.route.link
et, si le lien pouvait être modifié, onupdate: m.route.link
comme fonctions de cycle de vie sur le vnode routable. Dans la version 2.x, on utilise le composant m.route.Link
. Le sélecteur peut être spécifié via un attribut selector:
, si vous utilisez autre chose que m("a", ...)
. Les options peuvent être spécifiées via options:
, la désactivation via disabled:
, et d'autres attributs peuvent être spécifiés directement, y compris href:
(obligatoire). Le selector:
lui-même peut contenir n'importe quel sélecteur valide comme premier argument pour m
, et les attributs [href=...]
et [disabled]
peuvent être spécifiés dans le sélecteur ainsi que dans les options habituelles.
version 1.x
m('a', {
href: '/path',
oncreate: m.route.link,
});
m('button', {
href: '/path',
oncreate: m.route.link,
});
m('button.btn[href=/path]', {
oncreate: m.route.link,
});
version 2.x
m(m.route.Link, {
href: '/path',
});
m(m.route.Link, {
selector: 'button',
href: '/path',
});
m(m.route.Link, {
selector: 'button.btn[href=/path]',
});
Modifications des erreurs m.request
Dans la version 1.x, m.request
analysait les erreurs des appels JSON et assignait les propriétés de l'objet analysé résultant à la réponse. Ainsi, si vous receviez une réponse avec le statut 403 et un corps de {"code": "backoff", "timeout": 1000}
, l'erreur aurait deux propriétés assignées : err.code = "backoff"
et err.timeout = 1000
.
Dans la version 2.x, la réponse est assignée à une propriété response
sur le résultat, et une propriété code
contient le code de statut résultant. Donc, si vous receviez une réponse avec le statut 403 et un corps de {"code": "backoff", "timeout": 1000}
, l'erreur se verrait assigner deux propriétés : err.response = {code: "backoff", timeout: 1000}
et err.code = 403
.
m.withAttr
supprimé
Dans la version 1.x, les gestionnaires d'événements pouvaient utiliser oninput: m.withAttr("value", func)
et similaire. Dans la version 2.x, il suffit de lire les valeurs directement à partir de la cible de l'événement. Bien que cela fonctionnait avec les flux, l'idiome m.withAttr("value", stream)
était beaucoup moins courant que m.withAttr("value", prop)
. Par conséquent, m.withAttr
a perdu la plupart de son utilité et a été supprimé.
version 1.x
var value = '';
// Dans votre composant
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', function (v) {
value = v;
}),
});
// OU
var value = m.stream('');
// Dans votre composant
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', value),
});
version 2.x
var value = '';
// Dans votre composant
m('input[type=text]', {
value: value,
oninput: function (ev) {
value = ev.target.value;
},
});
// OU
var value = m.stream('');
// Dans votre composant
m('input[type=text]', {
value: value(),
oninput: function (ev) {
value(ev.target.value);
},
});
m.route.prefix
Dans la version 1.x, m.route.prefix
était une fonction appelée via m.route.prefix(prefix)
. C'est maintenant une propriété que vous définissez via m.route.prefix = prefix
.
version 1.x
m.route.prefix('/root');
version 2.x
m.route.prefix = '/root';
Paramètres et corps de m.request
/m.jsonp
Les options data
et useBody
ont été refactorisées en params
(les 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). Cela offre un meilleur contrôle sur la requête réelle envoyée et permet à la fois d'interpoler dans les paramètres de requête avec les requêtes POST
et de créer des requêtes GET
avec des corps.
m.jsonp
, n'ayant pas de "corps" significatif, utilise simplement params
. Le renommage de data
en params
est donc suffisant pour cette méthode.
version 1.x
m.request('https://example.com/api/user/:id', {
method: 'GET',
data: { id: user.id },
});
m.request('https://example.com/api/user/create', {
method: 'POST',
data: userData,
});
version 2.x
m.request('https://example.com/api/user/:id', {
method: 'GET',
params: { id: user.id },
});
m.request('https://example.com/api/user/create', {
method: 'POST',
body: userData,
});
Modèles de chemin
Dans la version 1.x, il existait trois syntaxes de modèle de chemin distinctes qui, bien que similaires, avaient 2 syntaxes conçues séparément et 3 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 les éléments sont encodés de manière inattendue, utilisez :path...
. C'est aussi simple que cela.
Concrètement, voici comment cela affecte chaque méthode :
URL m.request
et m.jsonp
, chemins m.route.set
Les composants de chemin dans la version 2.x sont échappés automatiquement lors de l'interpolation. Supposons que vous invoquiez m.route.set("/user/:name/photos/:id", {name: user.name, id: user.id})
. Auparavant, si user
était {name: "a/b", id: "c/d"}
, cela définirait la route sur /user/a%2Fb/photos/c/d
, mais cela la définira maintenant sur /user/a%2Fb/photos/c%2Fd
. Si vous voulez délibérément interpoler une clé non échappée, utilisez :key...
à la place.
Les clés dans la version 2.x ne peuvent contenir aucune instance de .
ou -
. Dans la version 1.x, elles pouvaient contenir n'importe quoi d'autre que /
.
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 2.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.
Motifs de route m.route
Les clés de chemin de la forme :key...
renvoient leur URL décodée dans la version 1.x, mais renvoient l'URL brute dans la version 2.x.
Auparavant, des éléments comme :key.md
étaient acceptés par erreur, avec la valeur du paramètre résultant définie sur keymd: "..."
. Ce n'est plus le cas : le .md
fait maintenant partie du motif, pas du nom.
Ordre d'appel des fonctions de cycle de vie
Dans la version 1.x, les fonctions de cycle de vie des attributs sur les vnodes de composant étaient appelées avant les propres fonctions de cycle de vie du composant dans tous les cas. Dans la version 2.x, ce n'est le cas que pour onbeforeupdate
. Il est donc impératif d'ajuster votre code en conséquence.
version 1.x
var Comp = {
oncreate: function () {
console.log('Component oncreate');
},
view: function () {
return m('div');
},
};
m.mount(document.body, {
view: function () {
return m(Comp, {
oncreate: function () {
console.log('Attrs oncreate');
},
});
},
});
// Résultat :
// Attrs oncreate
// Component oncreate
version 2.x
var Comp = {
oncreate: function () {
console.log('Component oncreate');
},
view: function () {
return m('div');
},
};
m.mount(document.body, {
view: function () {
return m(Comp, {
oncreate: function () {
console.log('Attrs oncreate');
},
});
},
});
// Résultat :
// Component oncreate
// Attrs oncreate
Synchronicité de m.redraw
m.redraw()
dans la version 2.x est toujours asynchrone. Vous pouvez demander spécifiquement un rafraîchissement synchrone via m.redraw.sync()
à condition qu'aucun rafraîchissement ne soit en cours.
Priorité des attributs de sélecteur
Dans la version 1.x, les attributs de sélecteur avaient priorité sur les attributs spécifiés dans l'objet d'attributs. Par exemple, m("[a=b]", {a: "c"}).attrs
renvoyait {a: "b"}
.
Dans la version 2.x, les attributs spécifiés dans l'objet d'attributs ont priorité sur les attributs de sélecteur. Par exemple, m("[a=b]", {a: "c"}).attrs
renvoie {a: "c"}
.
Notez qu'il s'agit techniquement d'un retour au comportement de la version 0.2.x.
Normalisation des enfants
Dans la version 1.x, les enfants des vnodes de composant étaient normalisés comme les autres vnodes. Dans la version 2.x, ce n'est plus le cas et vous devrez planifier en conséquence. Cela n'affecte pas la normalisation effectuée lors du rendu.
En-têtes m.request
Dans la version 1.x, Mithril.js définissait ces deux en-têtes sur toutes les requêtes non-GET
, mais seulement lorsque useBody
était défini sur true
(la valeur par défaut) et que les autres conditions énumérées étaient remplies :
Content-Type: application/json; charset=utf-8
pour les requêtes avec des corps JSONAccept: application/json, text/*
pour les requêtes attendant des réponses JSON
Dans la version 2.x, Mithril.js définit le premier pour toutes les requêtes avec des corps JSON qui sont != null
et l'omet par défaut sinon, et cela se fait indépendamment de la méthode choisie, y compris sur les requêtes GET
.
Le premier des deux en-têtes, Content-Type
, déclenchera une prélecture CORS car il n'est pas un en-tête de requête CORS sécurisé 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 avec cela, vous devrez peut-être remplacer l'en-tête en question en passant 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 permet d'é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 elle interdit intentionnellement JSON.
Paramètres de requête dans les fragments d'URL dans les routes
Dans la version 1.x, vous pouviez spécifier des paramètres de requête pour les routes à la fois dans la chaîne de requête et dans le fragment d'URL. Ainsi, m.route.set("/route?foo=1&bar=2")
, m.route.set("/route?foo=1#bar=2")
et m.route.set("/route#foo=1&bar=2")
étaient tous équivalents, et les attributs extraits d'eux auraient été {foo: "1", bar: "2"}
.
Dans la version 2.x, le contenu des fragments d'URL est ignoré mais conservé. Ainsi, les attributs extraits de chacun seraient les suivants :
m.route.set("/route?foo=1&bar=2")
→{foo: "1", bar: "2"}
m.route.set("/route?foo=1#bar=2")
→{foo: "1"}
m.route.set("/route#foo=1&bar=2")
→{}
La raison en est que les URL comme https://example.com/#!/route#key
sont techniquement invalides selon la spécification URL et étaient même invalides selon la RFC qui l'a précédée. Ce n'est qu'une bizarrerie de la spécification HTML qu'elles soient autorisées. (La spécification HTML aurait dû exiger que les ID et les fragments d'emplacement soient des fragments d'URL valides dès le début si elle voulait suivre la spécification.)
En bref, arrêtez d'utiliser des URL invalides !
Les clés
Dans la version 1.x, vous pouviez mélanger librement les vnodes avec et sans clé. Si le premier nœud a une clé, une comparaison avec clé est effectuée, en supposant que chaque élément a une clé et en ignorant simplement les éléments vides au fur et à mesure. Sinon, une comparaison itérative est effectuée, et si un nœud a une clé, il serait vérifié qu'il n'a pas changé en même temps que les balises et similaires sont vérifiées.
Dans la version 2.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 non-clés aux fins de cette vérification : ils ne sont plus ignorés.
Si vous devez contourner cette limitation, utilisez l'idiome d'un fragment contenant un seul vnode, comme [m("div", {key: whatever})]
.
m.version
supprimé
Il servait peu en général, et vous pouvez toujours le rajouter vous-même. Vous devriez préférer la détection de fonctionnalités pour savoir quelles fonctionnalités sont disponibles, et l'API version 2.x est conçue pour mieux permettre cela.