Skip to content
Mithril.js 2
Main Navigation GuideAPI

Français

English
简体中文
繁體中文
Español
Русский
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

Français

English
简体中文
繁體中文
Español
Русский
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

Apparence

Sidebar Navigation

Bien démarrer

Installation

Application Simple

Ressources

JSX

ES6+ sur les navigateurs anciens

Animations

Tests

Exemples

Intégration de librairies externes

Gestion des Chemins

Concepts clés

Nœuds du DOM virtuel

Composants

Méthodes de cycle de vie

Keys

Le système de rafraîchissement automatique

Divers

Comparaison des frameworks

Migration depuis la version 1.x

Migration depuis la v0.2.x

API

Sur cette page

Composants ​

Structure ​

Les composants sont un mécanisme permettant d'encapsuler des parties d'une vue afin de faciliter l'organisation et/ou la réutilisation du code.

Tout objet JavaScript possédant une méthode view est un composant Mithril.js. Les composants peuvent être utilisés via l'utilitaire m() :

javascript
// Définir votre composant
var Example = {
  view: function (vnode) {
    return m('div', 'Hello');
  },
};

// Utiliser votre composant
m(Example);

// HTML équivalent
// <div>Hello</div>

Méthodes de cycle de vie ​

Les composants peuvent avoir les mêmes méthodes de cycle de vie que les nœuds du DOM virtuel. Notez que vnode est passé comme argument à chaque méthode de cycle de vie, ainsi qu'à la méthode view (le vnode précédent étant également passé à onbeforeupdate).

javascript
var ComponentWithHooks = {
  oninit: function (vnode) {
    console.log('initialisé');
  },
  oncreate: function (vnode) {
    console.log('DOM créé');
  },
  onbeforeupdate: function (newVnode, oldVnode) {
    return true;
  },
  onupdate: function (vnode) {
    console.log('DOM mis à jour');
  },
  onbeforeremove: function (vnode) {
    console.log("L'animation de sortie peut démarrer");
    return new Promise(function (resolve) {
      // Appeler une fois l'animation terminée
      resolve();
    });
  },
  onremove: function (vnode) {
    console.log("Suppression de l'élément DOM");
  },
  view: function (vnode) {
    return 'hello';
  },
};

Comme d'autres types de nœuds du DOM virtuel, les composants peuvent avoir des méthodes de cycle de vie supplémentaires définies lorsqu'ils sont utilisés comme types de vnode.

javascript
function initialize(vnode) {
  console.log('initialisé comme vnode');
}

m(ComponentWithHooks, { oninit: initialize });

Les méthodes de cycle de vie dans les vnodes ne remplacent pas les méthodes de composant, ni vice versa. Les méthodes de cycle de vie des composants sont toujours exécutées après la méthode correspondante du vnode.

Veillez à ne pas utiliser les noms des méthodes de cycle de vie pour vos propres fonctions de rappel dans les vnodes.

Pour en savoir plus sur les méthodes de cycle de vie, consultez la page des méthodes de cycle de vie.

Transmission de données aux composants ​

Des données peuvent être transmises aux instances de composant en passant un objet attrs comme second paramètre à la fonction hyperscript.

javascript
m(Example, { name: 'Floyd' });

Ces données sont accessibles dans la vue du composant ou dans les méthodes de cycle de vie via vnode.attrs :

javascript
var Example = {
  view: function (vnode) {
    return m('div', 'Hello, ' + vnode.attrs.name);
  },
};

Note : Les méthodes de cycle de vie peuvent également être définies dans l'objet attrs. Vous devez donc éviter d'utiliser leurs noms pour vos propres callbacks, car ils seraient également invoqués par Mithril.js lui-même. Utilisez-les dans attrs uniquement si vous souhaitez spécifiquement les utiliser comme méthodes de cycle de vie.

État ​

Comme tous les nœuds du DOM virtuel, les vnodes de composant peuvent avoir un état. L'état du composant est utile pour prendre en charge les architectures orientées objet, pour l'encapsulation et pour la séparation des préoccupations.

Notez que, contrairement à de nombreux autres frameworks, la modification de l'état du composant ne déclenche pas de redessin ni de mises à jour du DOM. Au lieu de cela, les redessins sont effectués lorsque les gestionnaires d'événements se déclenchent, lorsque les requêtes HTTP effectuées par m.request se terminent ou lorsque le navigateur navigue vers différents itinéraires. Les mécanismes d'état des composants de Mithril.js existent simplement par commodité pour les applications.

Si un changement d'état se produit qui n'est pas le résultat de l'une des conditions ci-dessus (par exemple, après un setTimeout), vous pouvez utiliser m.redraw() pour déclencher manuellement un redessin.

État du composant de fermeture ​

Dans les exemples ci-dessus, chaque composant est défini comme un POJO (Plain Old JavaScript Object), qui est utilisé en interne par Mithril.js comme prototype pour les instances de ce composant. Il est possible d'utiliser l'état du composant avec un POJO (comme nous le verrons ci-dessous), mais ce n'est pas l'approche la plus propre ou la plus simple. Pour cela, nous utiliserons un composant de fermeture, qui est simplement une fonction enveloppe qui renvoie une instance de composant POJO, laquelle possède sa propre portée fermée.

Avec un composant de fermeture, l'état peut simplement être maintenu par des variables déclarées dans la fonction externe :

javascript
function ComponentWithState(initialVnode) {
  // Variable d'état du composant, unique à chaque instance
  var count = 0;

  // Instance de composant POJO : tout objet avec une
  // fonction view qui renvoie un vnode
  return {
    oninit: function (vnode) {
      console.log('initialiser un composant de fermeture');
    },
    view: function (vnode) {
      return m(
        'div',
        m('p', 'Count: ' + count),
        m(
          'button',
          {
            onclick: function () {
              count += 1;
            },
          },
          'Increment count'
        )
      );
    },
  };
}

Toutes les fonctions déclarées dans la fermeture ont également accès à ses variables d'état.

javascript
function ComponentWithState(initialVnode) {
  var count = 0;

  function increment() {
    count += 1;
  }

  function decrement() {
    count -= 1;
  }

  return {
    view: function (vnode) {
      return m(
        'div',
        m('p', 'Count: ' + count),
        m(
          'button',
          {
            onclick: increment,
          },
          'Increment'
        ),
        m(
          'button',
          {
            onclick: decrement,
          },
          'Decrement'
        )
      );
    },
  };
}

Les composants de fermeture sont utilisés de la même manière que les POJO, par exemple m(ComponentWithState, { passedData: ... }).

Un grand avantage des composants de fermeture est que nous n'avons pas à nous soucier de la liaison de this lors de l'attachement des callbacks de gestionnaires d'événements. En fait, this n'est jamais utilisé et nous n'avons jamais à penser aux ambiguïtés de contexte de this.

État du composant POJO ​

Il est généralement recommandé d'utiliser des fermetures pour gérer l'état du composant. Si, toutefois, vous avez une raison de gérer l'état dans un POJO, l'état d'un composant peut être consulté de trois manières : comme un modèle lors de l'initialisation, via vnode.state et via le mot-clé this dans les méthodes de composant.

À l'initialisation ​

Pour les composants POJO, l'objet composant est le prototype de chaque instance de composant. Toute propriété définie sur l'objet composant sera donc accessible en tant que propriété de vnode.state. Cela permet une initialisation simple de l'état du "modèle".

Dans l'exemple ci-dessous, data devient une propriété de l'objet vnode.state du composant ComponentWithInitialState.

javascript
var ComponentWithInitialState = {
  data: 'Initial content',
  view: function (vnode) {
    return m('div', vnode.state.data);
  },
};

m(ComponentWithInitialState);

// HTML équivalent
// <div>Initial content</div>

Via vnode.state ​

Comme vous pouvez le constater, l'état est également accessible via la propriété vnode.state, qui est disponible pour toutes les méthodes de cycle de vie ainsi que pour la méthode view d'un composant.

javascript
var ComponentWithDynamicState = {
  oninit: function (vnode) {
    vnode.state.data = vnode.attrs.text;
  },
  view: function (vnode) {
    return m('div', vnode.state.data);
  },
};

m(ComponentWithDynamicState, { text: 'Hello' });

// HTML équivalente
// <div>Hello</div>

Via le mot-clé this ​

L'état est également accessible via le mot-clé this, qui est disponible pour toutes les méthodes de cycle de vie ainsi que pour la méthode view d'un composant.

javascript
var ComponentUsingThis = {
  oninit: function (vnode) {
    this.data = vnode.attrs.text;
  },
  view: function (vnode) {
    return m('div', this.data);
  },
};

m(ComponentUsingThis, { text: 'Hello' });

// HTML équivalente
// <div>Hello</div>

Sachez que, lorsque vous utilisez des fonctions ES5, la valeur de this dans les fonctions anonymes imbriquées n'est pas celle de l'instance du composant. Il existe deux façons recommandées de contourner cette limitation JavaScript : utiliser des fonctions fléchées ou, si celles-ci ne sont pas prises en charge, utiliser vnode.state.

Classes ​

Si cela répond à vos besoins (comme dans les projets orientés objet), les composants peuvent également être écrits à l'aide de classes :

javascript
class ClassComponent {
  constructor(vnode) {
    this.kind = 'class component';
  }
  view() {
    return m('div', `Hello from a ${this.kind}`);
  }
  oncreate() {
    console.log(`A ${this.kind} was created`);
  }
}

Les composants de classe doivent définir une méthode view(), détectée via .prototype.view, pour que l'arbre soit rendu.

Ils peuvent être utilisés de la même manière que les composants réguliers.

javascript
// EXEMPLE : via m.render
m.render(document.body, m(ClassComponent));

// EXEMPLE : via m.mount
m.mount(document.body, ClassComponent);

// EXEMPLE : via m.route
m.route(document.body, '/', {
  '/': ClassComponent,
});

// EXEMPLE : composition de composants
class AnotherClassComponent {
  view() {
    return m('main', [m(ClassComponent)]);
  }
}

État du composant de classe ​

Avec les classes, l'état peut être géré par les propriétés et les méthodes de l'instance de classe, et accessible via this :

javascript
class ComponentWithState {
  constructor(vnode) {
    this.count = 0;
  }
  increment() {
    this.count += 1;
  }
  decrement() {
    this.count -= 1;
  }
  view() {
    return m(
      'div',
      m('p', 'Count: ', this.count),
      m(
        'button',
        {
          onclick: () => {
            this.increment();
          },
        },
        'Increment'
      ),
      m(
        'button',
        {
          onclick: () => {
            this.decrement();
          },
        },
        'Decrement'
      )
    );
  }
}

Notez que nous devons utiliser des fonctions fléchées pour les callbacks des gestionnaires d'événements afin que le contexte this puisse être référencé correctement.

Combinaison de différents types de composants ​

Les composants peuvent être mélangés librement. Un composant de classe peut avoir des composants de fermeture ou POJO comme enfants, etc.

Attributs spéciaux ​

Mithril.js attribue une sémantique spéciale à plusieurs clés de propriété. Vous devez donc normalement éviter de les utiliser dans les attributs de composant normaux.

  • Méthodes de cycle de vie : oninit, oncreate, onbeforeupdate, onupdate, onbeforeremove et onremove
  • key, qui est utilisé pour suivre l'identité dans les fragments à clés
  • tag, qui est utilisé pour distinguer les vnodes des objets d'attributs normaux et d'autres éléments qui ne sont pas des objets vnode.

Éviter les anti-patterns ​

Bien que Mithril.js soit flexible, certains modèles de code sont à déconseiller.

Éviter les composants "lourds" (fat components) ​

En général, un composant « lourd » est un composant qui possède des méthodes d'instance personnalisées. En d'autres termes, vous devez éviter d'attacher des fonctions à vnode.state ou this. Il est extrêmement rare d'avoir une logique qui s'intègre logiquement dans une méthode d'instance de composant et qui ne peut pas être réutilisée par d'autres composants. Il est relativement courant que cette logique puisse être nécessaire à un autre composant à l'avenir.

Il est plus facile de refactoriser le code si cette logique est placée dans la couche de données plutôt que si elle est liée à un état de composant.

Considérez cet exemple de composant "lourd" :

javascript
// views/Login.js
// À ÉVITER :
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 },
        'Login'
      ),
    ]);
  },
};

Normalement, dans le contexte d'une application plus vaste, un composant de connexion comme celui ci-dessus existe aux côtés des composants d'enregistrement d'utilisateur et de récupération de mot de passe. Imaginez que nous voulions être en mesure de pré-remplir le champ d'adresse e-mail lors de la navigation de l'écran de connexion vers les écrans d'enregistrement ou de récupération de mot de passe (ou vice versa), afin que l'utilisateur n'ait pas besoin de retaper son adresse e-mail s'il s'est trompé de page (ou peut-être voulez-vous rediriger l'utilisateur vers le formulaire d'inscription si un nom d'utilisateur n'est pas trouvé).

Immédiatement, nous constatons qu'il est difficile de partager les champs username et password de ce composant avec un autre. En effet, le composant "lourd" encapsule son état, ce qui, par définition, rend cet état difficile d'accès de l'extérieur.

Il est plus logique de refactoriser ce composant et de sortir le code d'état du composant et de le placer dans la couche de données de l'application. Cela peut être aussi simple que de créer un nouveau module :

javascript
// models/Auth.js
// PRÉFÉRER :
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;

Ensuite, nous pouvons nettoyer le composant :

javascript
// views/Login.js
// PRÉFÉRER :
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,
        },
        'Login'
      ),
    ]);
  },
};

De cette façon, le module Auth est désormais la source de vérité pour l'état lié à l'authentification, et un composant Register peut facilement accéder à ces données, et même réutiliser des méthodes comme canSubmit, si nécessaire. De plus, si un code de validation est requis (par exemple, pour le champ d'adresse e-mail), il vous suffit de modifier setEmail, et cette modification effectuera la validation de l'adresse e-mail pour tout composant qui modifie un champ d'adresse e-mail.

En prime, notez que nous n'avons plus besoin d'utiliser .bind pour conserver une référence à l'état pour les gestionnaires d'événements du composant.

Ne pas transférer vnode.attrs lui-même à d'autres vnodes ​

Parfois, vous pouvez souhaiter conserver une interface flexible et simplifier votre implémentation en transférant des attributs à un composant ou un élément enfant particulier, comme la fenêtre modale de Bootstrap. Il peut être tentant de transférer les attributs d'un vnode comme ceci :

javascript
// À ÉVITER :
var Modal = {
  // ...
  view: function (vnode) {
    return m('.modal[tabindex=-1][role=dialog]', vnode.attrs, [
      //         transfert de `vnode.attrs` ici ^
      // ...
    ]);
  },
};

Si vous le faites comme ci-dessus, vous pourriez rencontrer des problèmes lors de son utilisation :

javascript
var MyModal = {
  view: function () {
    return m(
      Modal,
      {
        // Cela le bascule deux fois, donc il ne s'affiche pas
        onupdate: function (vnode) {
          if (toggle) $(vnode.dom).modal('toggle');
        },
      },
      [
        // ...
      ]
    );
  },
};

Au lieu de cela, vous devez transférer des attributs uniques dans les vnodes :

javascript
// PRÉFÉRER :
var Modal = {
  // ...
  view: function (vnode) {
    return m('.modal[tabindex=-1][role=dialog]', vnode.attrs.attrs, [
      //              transfert de `attrs:` ici ^
      // ...
    ]);
  },
};

// Exemple
var MyModal = {
  view: function () {
    return m(Modal, {
      attrs: {
        // Cela le bascule une fois
        onupdate: function (vnode) {
          if (toggle) $(vnode.dom).modal('toggle');
        },
      },
      // ...
    });
  },
};

Ne pas manipuler children ​

Si un composant a une opinion sur la façon dont il applique les attributs ou les enfants, vous devez passer à l'utilisation d'attributs personnalisés.

Il est souvent souhaitable de définir plusieurs ensembles d'enfants, par exemple, si un composant a un titre et un corps configurables.

Évitez de déstructurer la propriété children à cette fin.

javascript
// À ÉVITER :
var Header = {
  view: function (vnode) {
    return m('.section', [
      m('.header', vnode.children[0]),
      m('.tagline', vnode.children[1]),
    ]);
  },
};

m(Header, [m('h1', 'My title'), m('h2', 'Lorem ipsum')]);

// cas d'utilisation de consommation maladroit
m(Header, [
  [m('h1', 'My title'), m('small', 'A small note')],
  m('h2', 'Lorem ipsum'),
]);

Le composant ci-dessus enfreint l'hypothèse selon laquelle les enfants seront affichés dans le même format contigu que celui dans lequel ils sont reçus. Il est difficile de comprendre le composant sans lire son implémentation. Utilisez plutôt des attributs comme paramètres nommés et réservez children pour un contenu enfant uniforme :

javascript
// PRÉFÉRER :
var BetterHeader = {
  view: function (vnode) {
    return m('.section', [
      m('.header', vnode.attrs.title),
      m('.tagline', vnode.attrs.tagline),
    ]);
  },
};

m(BetterHeader, {
  title: m('h1', 'My title'),
  tagline: m('h2', 'Lorem ipsum'),
});

// cas d'utilisation de consommation plus clair
m(BetterHeader, {
  title: [m('h1', 'My title'), m('small', 'A small note')],
  tagline: m('h2', 'Lorem ipsum'),
});

Définir les composants de manière statique, les appeler de manière dynamique ​

Éviter de créer des définitions de composants à l'intérieur des vues ​

Si vous créez un composant à partir d'une méthode view (soit directement en ligne, soit en appelant une fonction qui le fait), chaque redessin produira un clone différent du composant. Lors de la différenciation des vnodes de composant, si le composant référencé par le nouveau vnode n'est pas strictement égal à celui référencé par l'ancien composant, les deux sont supposés être des composants différents, même s'ils exécutent finalement un code équivalent. Cela signifie que les composants créés dynamiquement via une fabrique seront toujours recréés à partir de zéro.

Pour cette raison, vous devez éviter de recréer des composants. Utilisez plutôt les composants de manière idiomatique.

javascript
// À ÉVITER :
var ComponentFactory = function (greeting) {
  // crée un nouveau composant à chaque appel
  return {
    view: function () {
      return m('div', greeting);
    },
  };
};
m.render(document.body, m(ComponentFactory('hello')));
// appeler une deuxième fois recrée div à partir de zéro plutôt que de ne rien faire
m.render(document.body, m(ComponentFactory('hello')));

// PRÉFÉRER :
var Component = {
  view: function (vnode) {
    return m('div', vnode.attrs.greeting);
  },
};
m.render(document.body, m(Component, { greeting: 'hello' }));
// appeler une deuxième fois ne modifie pas le DOM
m.render(document.body, m(Component, { greeting: 'hello' }));

Éviter de créer des instances de composant en dehors des vues ​

Inversement, pour des raisons similaires, si une instance de composant est créée en dehors d'une vue, les futurs redessins effectueront une vérification d'égalité sur le nœud et le sauteront. Par conséquent, les instances de composant doivent toujours être créées à l'intérieur des vues :

javascript
// À ÉVITER :
var Counter = {
  count: 0,
  view: function (vnode) {
    return m(
      'div',
      m('p', 'Count: ' + vnode.state.count),

      m(
        'button',
        {
          onclick: function () {
            vnode.state.count++;
          },
        },
        'Increase count'
      )
    );
  },
};

var counter = m(Counter);

m.mount(document.body, {
  view: function (vnode) {
    return [m('h1', 'My app'), counter];
  },
});

Dans l'exemple ci-dessus, cliquer sur le bouton du composant compteur augmentera son nombre d'état, mais sa vue ne sera pas déclenchée car le vnode représentant le composant partage la même référence, et par conséquent, le processus de rendu ne les différencie pas. Vous devez toujours appeler les composants dans la vue pour vous assurer qu'un nouveau vnode est créé :

javascript
// PRÉFÉRER :
var Counter = {
  count: 0,
  view: function (vnode) {
    return m(
      'div',
      m('p', 'Count: ' + vnode.state.count),

      m(
        'button',
        {
          onclick: function () {
            vnode.state.count++;
          },
        },
        'Increase count'
      )
    );
  },
};

m.mount(document.body, {
  view: function (vnode) {
    return [m('h1', 'My app'), m(Counter)];
  },
});
Pager
Page précédenteNœuds du DOM virtuel
Page suivanteMéthodes de cycle de vie

Publié sous la licence MIT.

Copyright (c) 2024 Mithril Contributors

https://mithril.js.org/components.html

Publié sous la licence MIT.

Copyright (c) 2024 Mithril Contributors