Skip to content
Mithril.js 2
Main Navigation GuidaAPI

Italiano

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

Italiano

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

Aspetto

Sidebar Navigation

Per iniziare

Installazione di Mithril.js

Applicazione semplice

Risorse

JSX

ES6+ su browser legacy

Animazioni

Testing

Esempi

Integrazione di terze parti

Gestione dei Percorsi

Concetti chiave

Nodi del DOM virtuale

Componenti

Metodi del Ciclo di Vita

Chiavi

Il sistema di aggiornamento automatico

Varie

Confronto tra i Framework

Migrazione da v1.x

Migrazione da v0.2.x

API

In questa pagina

Componenti ​

Struttura ​

I componenti sono un meccanismo per incapsulare parti di una vista, rendendo il codice più facile da organizzare e/o riutilizzare.

Qualsiasi oggetto JavaScript che abbia un metodo view è un componente Mithril.js. I componenti possono essere utilizzati tramite l'utility m():

javascript
// definisci il tuo componente
var Example = {
  view: function (vnode) {
    return m('div', 'Hello');
  },
};

// utilizza il tuo componente
m(Example);

// HTML equivalente
// <div>Hello</div>

Metodi del ciclo di vita ​

I componenti possono avere gli stessi metodi del ciclo di vita dei nodi del DOM virtuale. Si noti che vnode viene passato come argomento a ciascun metodo del ciclo di vita, inclusa view (a onbeforeupdate viene passato anche il precedente vnode):

javascript
var ComponentWithHooks = {
  oninit: function (vnode) {
    console.log('initialized');
  },
  oncreate: function (vnode) {
    console.log('DOM created');
  },
  onbeforeupdate: function (newVnode, oldVnode) {
    return true;
  },
  onupdate: function (vnode) {
    console.log('DOM updated');
  },
  onbeforeremove: function (vnode) {
    console.log('exit animation can start');
    return new Promise(function (resolve) {
      // chiama dopo che l'animazione è completa
      resolve();
    });
  },
  onremove: function (vnode) {
    console.log('removing DOM element');
  },
  view: function (vnode) {
    return 'hello';
  },
};

Come altri tipi di nodi del DOM virtuale, i componenti possono avere metodi del ciclo di vita aggiuntivi definiti quando vengono utilizzati come tipi di vnode.

javascript
function initialize(vnode) {
  console.log('initialized as vnode');
}

m(ComponentWithHooks, { oninit: initialize });

I metodi del ciclo di vita nei vnode non sovrascrivono i metodi dei componenti, né viceversa. I metodi del ciclo di vita dei componenti vengono sempre eseguiti dopo il metodo corrispondente del vnode.

Prestare attenzione a non riutilizzare i nomi dei metodi del ciclo di vita per le proprie funzioni di callback nei vnode.

Per saperne di più sui metodi del ciclo di vita, vedi la pagina dei metodi del ciclo di vita.

Passaggio di dati ai componenti ​

I dati possono essere passati alle istanze dei componenti passando un oggetto attrs come secondo parametro nella funzione hyperscript:

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

Questi dati sono accessibili nella vista del componente o nei metodi del ciclo di vita tramite vnode.attrs:

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

NOTA: I metodi del ciclo di vita possono anche essere definiti nell'oggetto attrs, quindi è necessario evitare di utilizzare i loro nomi per le proprie callback poiché verrebbero invocati anche da Mithril.js stesso. Usarli in attrs solo quando si desidera specificamente utilizzarli come metodi del ciclo di vita.

Stato ​

Come tutti i nodi del DOM virtuale, i vnode dei componenti possono avere uno stato. Lo stato del componente è utile per supportare architetture orientate agli oggetti, per l'incapsulamento e per separare le responsabilità.

Si noti che, a differenza di molti altri framework, la modifica dello stato del componente non attiva redraws o aggiornamenti del DOM. Invece, i redraws vengono eseguiti quando vengono attivati i gestori di eventi, quando le richieste HTTP effettuate da m.request vengono completate o quando il browser naviga verso route diverse. I meccanismi di stato dei componenti di Mithril.js esistono semplicemente come una comodità per le applicazioni.

Se si verifica una modifica dello stato che non è il risultato di nessuna delle condizioni di cui sopra (ad esempio, dopo un setTimeout), è possibile utilizzare m.redraw() per attivare manualmente un redraw.

Stato del componente Closure ​

Negli esempi precedenti, ogni componente è definito come un POJO (Plain Old JavaScript Object), che viene utilizzato internamente da Mithril.js come prototipo per le istanze di quel componente. È possibile utilizzare lo stato del componente con un POJO (come discuteremo di seguito), ma non è l'approccio più pulito o più semplice. Per questo useremo un componente closure, che è semplicemente una funzione di avvolgimento che restituisce un'istanza di componente POJO, che a sua volta ha un proprio scope privato.

Con un componente closure, lo stato può essere semplicemente mantenuto da variabili dichiarate all'interno della funzione esterna:

javascript
function ComponentWithState(initialVnode) {
  // Variabile di stato del componente, univoca per ogni istanza
  var count = 0;

  // Istanza di componente POJO: qualsiasi oggetto con una
  // funzione view che restituisce un vnode
  return {
    oninit: function (vnode) {
      console.log('init a closure component');
    },
    view: function (vnode) {
      return m(
        'div',
        m('p', 'Count: ' + count),
        m(
          'button',
          {
            onclick: function () {
              count += 1;
            },
          },
          'Increment count'
        )
      );
    },
  };
}

Qualsiasi funzione dichiarata all'interno della closure ha anche accesso alle sue variabili di stato.

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'
        )
      );
    },
  };
}

I componenti closure vengono utilizzati nello stesso modo dei POJO, ad esempio m(ComponentWithState, { passedData: ... }).

Un grande vantaggio dei componenti closure è che non dobbiamo preoccuparci di associare this quando si collegano le callback del gestore di eventi. In effetti this non viene mai usato e non dobbiamo mai pensare alle ambiguità del contesto this.

Stato del componente POJO ​

Si raccomanda generalmente di utilizzare le closure per la gestione dello stato del componente. Se, tuttavia, si ha motivo di gestire lo stato in un POJO, lo stato di un componente può essere accessibile in tre modi diversi: come modello all'inizializzazione, tramite vnode.state e tramite la parola chiave this nei metodi del componente.

All'inizializzazione ​

Per i componenti POJO, l'oggetto componente è il prototipo di ogni istanza del componente, quindi qualsiasi proprietà definita sull'oggetto componente sarà accessibile come proprietà di vnode.state. Ciò permette una semplice inizializzazione dello stato come 'modello'.

Nell'esempio seguente, data diventa una proprietà dell'oggetto vnode.state del componente ComponentWithInitialState.

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

m(ComponentWithInitialState);

// HTML equivalente
// <div>Initial content</div>

Tramite vnode.state ​

Come puoi vedere, lo stato può anche essere accessibile tramite la proprietà vnode.state, che è disponibile per tutti i metodi del ciclo di vita così come per il metodo view di un componente.

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 equivalente
// <div>Hello</div>

Tramite la parola chiave this ​

Lo stato può anche essere accessibile tramite la parola chiave this, che è disponibile per tutti i metodi del ciclo di vita così come per il metodo view di un componente.

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

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

// HTML equivalente
// <div>Hello</div>

Bisogna tenere presente che quando si utilizzano funzioni ES5, il valore di this nelle funzioni anonime nidificate non è l'istanza del componente. Ci sono due modi raccomandati per aggirare questa limitazione di JavaScript: usare le arrow functions oppure, se queste non sono supportate, usare vnode.state.

Classi ​

Se si adatta alle tue esigenze (come nei progetti orientati agli oggetti), i componenti possono anche essere scritti usando le classi:

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`);
  }
}

I componenti di classe devono definire un metodo view(), rilevato tramite .prototype.view, per ottenere l'albero da renderizzare.

Possono essere usati nello stesso modo dei componenti normali.

javascript
// ESEMPIO: tramite m.render
m.render(document.body, m(ClassComponent));

// ESEMPIO: tramite m.mount
m.mount(document.body, ClassComponent);

// ESEMPIO: tramite m.route
m.route(document.body, '/', {
  '/': ClassComponent,
});

// ESEMPIO: composizione di componenti
class AnotherClassComponent {
  view() {
    return m('main', [m(ClassComponent)]);
  }
}

Stato del componente di classe ​

Con le classi, lo stato può essere gestito dalle proprietà e dai metodi dell'istanza della classe e accessibile tramite 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'
      )
    );
  }
}

Si noti che dobbiamo usare le arrow functions per le callback del gestore di eventi in modo che il contesto this possa essere referenziato correttamente.

Combinazione di tipi di componenti ​

I componenti possono essere combinati. Un componente di classe può avere componenti closure o POJO come figli, ecc.

Attributi speciali ​

Mithril.js attribuisce una semantica speciale a diverse chiavi di proprietà, quindi normalmente dovresti evitare di usarle negli attributi normali dei componenti.

  • Metodi del ciclo di vita: oninit, oncreate, onbeforeupdate, onupdate, onbeforeremove e onremove
  • key, che viene utilizzato per tracciare l'identità nei frammenti con chiave
  • tag, che viene utilizzato per distinguere i vnode dagli oggetti attributi normali e da altre entità che sono oggetti non-vnode.

Evitare anti-pattern ​

Sebbene Mithril.js sia flessibile, alcuni pattern di codice sono sconsigliati:

Evitare componenti pesanti ​

In generale, un componente "pesante" (fat) è un componente che ha metodi di istanza personalizzati. In altre parole, dovresti evitare di collegare funzioni a vnode.state o this. È estremamente raro avere una logica che si adatti logicamente a un metodo di istanza del componente e che non possa essere riutilizzata da altri componenti. È relativamente comune che tale logica possa essere necessaria da un componente diverso in futuro.

È più facile rifattorizzare il codice se quella logica è posizionata nel livello dati piuttosto che se è legata a uno stato del componente.

Considera questo componente pesante:

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

Normalmente, nel contesto di un'applicazione più grande, un componente di login come quello sopra esiste insieme a componenti per la registrazione dell'utente e il recupero della password. Immagina di voler essere in grado di precompilare il campo email quando si naviga dalla schermata di login alle schermate di registrazione o di recupero della password (o viceversa), in modo che l'utente non debba digitare nuovamente la propria email se si è imbattuto nella pagina sbagliata (o forse vuoi indirizzare l'utente al modulo di registrazione se non viene trovato un username).

Vediamo subito che condividere i valori dei campi username e password da questo componente a un altro è difficile. Questo perché il componente 'pesante' incapsula il proprio stato, il che per definizione rende questo stato difficile da accedere dall'esterno.

È più opportuno rifattorizzare questo componente ed estrarre il codice di stato dal componente e nel livello dati dell'applicazione. Questo può essere semplice come creare un nuovo modulo:

javascript
// models/Auth.js
// PREFERIRE
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;

Quindi, possiamo pulire il componente:

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

In questo modo, il modulo Auth è ora la fonte di verità per lo stato relativo all'autenticazione e un componente Register può facilmente accedere a questi dati e persino riutilizzare metodi come canSubmit, se necessario. Inoltre, se è richiesto un codice di validazione (ad esempio, per il campo email), è sufficiente modificare setEmail e tale modifica eseguirà la validazione dell'email per qualsiasi componente che modifica un campo email.

Come bonus, si noti che non abbiamo più bisogno di usare .bind per mantenere un riferimento allo stato per i gestori di eventi del componente.

Non inoltrare vnode.attrs stesso ad altri vnodes ​

A volte, per mantenere un'interfaccia flessibile e semplificare l'implementazione, si potrebbe voler inoltrare gli attributi a un particolare componente o elemento figlio, in questo caso il modal di Bootstrap. Potrebbe essere allettante inoltrare gli attributi di un vnode in questo modo:

javascript
// EVITARE
var Modal = {
  // ...
  view: function (vnode) {
    return m('.modal[tabindex=-1][role=dialog]', vnode.attrs, [
      //         inoltrando `vnode.attrs` qui ^
      // ...
    ]);
  },
};

Se lo fai come sopra, potresti riscontrare problemi quando lo usi:

javascript
var MyModal = {
  view: function () {
    return m(
      Modal,
      {
        // Questo lo attiva due volte, quindi non viene visualizzato
        onupdate: function (vnode) {
          if (toggle) $(vnode.dom).modal('toggle');
        },
      },
      [
        // ...
      ]
    );
  },
};

Invece, dovresti inoltrare singoli attributi nei vnode:

javascript
// PREFERIRE
var Modal = {
  // ...
  view: function (vnode) {
    return m('.modal[tabindex=-1][role=dialog]', vnode.attrs.attrs, [
      //              inoltrando `attrs:` qui ^
      // ...
    ]);
  },
};

// Esempio
var MyModal = {
  view: function () {
    return m(Modal, {
      attrs: {
        // Questo lo attiva una volta
        onupdate: function (vnode) {
          if (toggle) $(vnode.dom).modal('toggle');
        },
      },
      // ...
    });
  },
};

Non manipolare children ​

Se un componente gestisce in modo specifico gli attributi o i children, dovresti passare all'uso di attributi personalizzati.

Spesso è desiderabile definire più set di children, ad esempio, se un componente ha un titolo e un corpo configurabili.

Evita di destrutturare la proprietà children per questo scopo.

javascript
// EVITARE
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')]);

// caso d'uso di consumo scomodo
m(Header, [
  [m('h1', 'My title'), m('small', 'A small note')],
  m('h2', 'Lorem ipsum'),
]);

Il componente sopra rompe l'assunzione che i children verranno emessi nello stesso formato contiguo in cui vengono ricevuti. È difficile capire il componente senza leggere la sua implementazione. Invece, usa gli attributi come parametri denominati e riserva children per contenuti figlio uniformi:

javascript
// PREFERIRE
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'),
});

// caso d'uso di consumo più chiaro
m(BetterHeader, {
  title: [m('h1', 'My title'), m('small', 'A small note')],
  tagline: m('h2', 'Lorem ipsum'),
});

Definisci i componenti staticamente, chiamali dinamicamente ​

Evita di creare definizioni di componenti all'interno delle view ​

Se crei un componente dall'interno di un metodo view (direttamente inline o chiamando una funzione che lo fa), ogni redraw avrà un clone diverso del componente. Quando si confrontano i vnode dei componenti, se il componente a cui fa riferimento il nuovo vnode non è strettamente uguale a quello a cui fa riferimento il vecchio componente, si presume che i due siano componenti diversi anche se alla fine eseguono codice equivalente. Ciò significa che i componenti creati dinamicamente tramite una factory verranno sempre ricreati da zero.

Per questo motivo è consigliabile evitare di ricreare i componenti. Invece, utilizzali nel modo standard.

javascript
// EVITARE
var ComponentFactory = function (greeting) {
  // crea un nuovo componente ad ogni chiamata
  return {
    view: function () {
      return m('div', greeting);
    },
  };
};
m.render(document.body, m(ComponentFactory('hello')));
// chiamare una seconda volta ricrea div da zero invece di non fare nulla
m.render(document.body, m(ComponentFactory('hello')));

// PREFERIRE
var Component = {
  view: function (vnode) {
    return m('div', vnode.attrs.greeting);
  },
};
m.render(document.body, m(Component, { greeting: 'hello' }));
// chiamare una seconda volta non modifica il DOM
m.render(document.body, m(Component, { greeting: 'hello' }));

Evita di creare istanze di componenti al di fuori delle view ​

Al contrario, per motivi analoghi, se un'istanza di componente viene creata al di fuori di una view, i successivi redraw eseguiranno una verifica di uguaglianza sul nodo e lo ignoreranno. Pertanto, le istanze dei componenti dovrebbero sempre essere create all'interno delle view:

javascript
// EVITARE
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];
  },
});

Nell'esempio sopra, fare clic sul pulsante del componente contatore aumenterà il suo conteggio di stato, ma la sua view non verrà attivata perché il vnode che rappresenta il componente condivide lo stesso riferimento e quindi il processo di rendering non li confronta. Dovresti sempre chiamare i componenti nella view per assicurarti che venga creato un nuovo vnode:

javascript
// PREFERIRE
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
Pagina precedenteNodi del DOM virtuale
Pagina successivaMetodi del Ciclo di Vita

Rilasciato sotto la licenza MIT.

Copyright (c) 2024 Mithril Contributors

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

Rilasciato sotto la licenza MIT.

Copyright (c) 2024 Mithril Contributors