Skip to content
Mithril.js 2
Main Navigation AnleitungAPI

Deutsch

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

Deutsch

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

Aussehen

Sidebar Navigation

Erste Schritte

Installation

Einfache Anwendung

Ressourcen

JSX

ES6+ in älteren Browsern

Animationen

Testen

Beispiele

Integration von Drittanbietern

Pfadverarbeitung

Schlüsselkonzepte

Virtuelle DOM Knoten

Komponenten

Lebenszyklus-Methoden

Keys

Das Auto-Redraw-System

Sonstiges

Framework-Vergleich

Migration von v1.x

Migration von v0.2.x auf v2.x

API

Auf dieser Seite

Komponenten ​

Struktur ​

Komponenten sind ein Mechanismus, um Teile einer Ansicht zu kapseln, um Code einfacher zu organisieren und/oder wiederzuverwenden.

Jedes JavaScript-Objekt, das eine view-Methode besitzt, ist eine Mithril.js-Komponente. Komponenten können über das m()-Utility verwendet werden:

javascript
// Definiere deine Komponente
var Example = {
  view: function (vnode) {
    return m('div', 'Hello');
  },
};

// Verwende deine Komponente
m(Example);

// Äquivalentes HTML
// <div>Hello</div>

Lebenszyklusmethoden ​

Komponenten können die gleichen Lebenszyklusmethoden wie virtuelle DOM-Knoten haben. Beachte, dass vnode als Argument an jede Lebenszyklusmethode übergeben wird, sowie an view (wobei der vorherige vnode zusätzlich an onbeforeupdate übergeben wird):

javascript
var ComponentWithHooks = {
  oninit: function (vnode) {
    console.log('initialisiert wurde');
  },
  oncreate: function (vnode) {
    console.log('DOM erstellt');
  },
  onbeforeupdate: function (newVnode, oldVnode) {
    return true;
  },
  onupdate: function (vnode) {
    console.log('DOM aktualisiert');
  },
  onbeforeremove: function (vnode) {
    console.log('Exit-Animation kann beginnen');
    return new Promise(function (resolve) {
      // Aufruf nach Abschluss der Animation
      resolve();
    });
  },
  onremove: function (vnode) {
    console.log('DOM-Element wird entfernt');
  },
  view: function (vnode) {
    return 'hello';
  },
};

Wie andere Arten von virtuellen DOM-Knoten können Komponenten zusätzliche Lebenszyklusmethoden definiert haben, wenn sie als vnode-Typen verwendet werden.

javascript
function initialize(vnode) {
  console.log('als vnode initialisiert');
}

m(ComponentWithHooks, { oninit: initialize });

Lebenszyklusmethoden in vnodes überschreiben keine Komponentenmethoden und umgekehrt. Komponenten-Lebenszyklusmethoden werden immer nach der entsprechenden Methode des vnodes ausgeführt.

Achte darauf, dass du keine Lebenszyklusmethodennamen für deine eigenen Callback-Funktionsnamen in vnodes verwendest.

Um mehr über Lebenszyklusmethoden zu erfahren, siehe die Seite Lebenszyklusmethoden.

Daten an Komponenten übergeben ​

Daten können an Komponenteninstanzen übergeben werden, indem ein attrs-Objekt als zweiter Parameter in der Hyperscript-Funktion verwendet wird:

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

Auf diese Daten kann in der Ansicht oder den Lebenszyklusmethoden der Komponente über vnode.attrs zugegriffen werden:

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

HINWEIS: Lebenszyklusmethoden können auch im attrs-Objekt definiert werden. Vermeide es daher, ihre Namen für eigene Rückruffunktionen zu verwenden, da diese auch von Mithril.js selbst aufgerufen würden. Verwende sie in attrs nur, wenn du sie explizit als Lebenszyklusmethoden verwenden möchtest.

State ​

Wie alle virtuellen DOM-Knoten können auch Komponenten-VNodes einen State haben. Der Komponenten-State ist nützlich für objektorientierte Architekturen, Kapselung und Trennung von Verantwortlichkeiten.

Im Gegensatz zu vielen anderen Frameworks ist zu beachten, dass das Verändern des Komponenten-States keine Redraws oder DOM-Aktualisierungen auslöst. Stattdessen werden Redraws durchgeführt, wenn Event-Handler ausgelöst werden, wenn HTTP-Requests, die von m.request gestellt werden, abgeschlossen sind oder wenn der Browser zu anderen Routen navigiert. Die State-Mechanismen von Mithril.js existieren lediglich als Komfortfunktion für Anwendungen.

Wenn eine State-Änderung auftritt, die nicht auf eine der oben genannten Bedingungen zurückzuführen ist (z. B. nach einem setTimeout), kann man m.redraw() verwenden, um manuell ein Redraw auszulösen.

Closure-Komponenten-State ​

In den obigen Beispielen ist jede Komponente als POJO (Plain Old JavaScript Object) definiert, das von Mithril.js intern als Prototyp für die Instanzen dieser Komponente verwendet wird. Es ist möglich, den Komponenten-State mit einem POJO zu verwenden, aber das ist nicht der sauberste oder einfachste Ansatz. Dafür verwenden wir eine Closure-Komponente, die einfach eine Wrapper-Funktion ist, die eine POJO-Komponenteninstanz zurückgibt, die wiederum ihren eigenen, geschlossenen Scope trägt.

Bei Closure-Komponenten kann der State einfach durch Variablen verwaltet werden, die innerhalb der äußeren Funktion deklariert sind:

javascript
function ComponentWithState(initialVnode) {
  // State-Variable der Komponente, eindeutig für jede Instanz
  var count = 0;

  // POJO-Komponenteninstanz: jedes Objekt mit einer
  // View-Funktion, die einen vnode zurückgibt
  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'
        )
      );
    },
  };
}

Alle Funktionen, die innerhalb der Closure deklariert werden, haben ebenfalls Zugriff auf ihre State-Variablen.

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

Closure-Komponenten können wie POJOs verwendet werden, z. B. m(ComponentWithState, { passedData: ... }).

Ein großer Vorteil von Closure-Komponenten ist, dass wir uns keine Gedanken über das Binden von this machen müssen, wenn wir Event-Handler-Rückrufe anhängen. Tatsächlich wird this überhaupt nicht verwendet und wir müssen nie über this-Kontext-Unklarheiten nachdenken.

POJO-Komponenten-State ​

Es wird empfohlen, Closures zur Verwaltung des Komponenten-States zu verwenden. Wenn man jedoch einen Grund hat, den State in einem POJO zu verwalten, kann auf den State einer Komponente auf drei Arten zugegriffen werden: als Blueprint bei der Initialisierung, über vnode.state und über das this-Keyword in Komponentenmethoden.

Bei der Initialisierung ​

Für POJO-Komponenten ist das Komponentenobjekt der Prototyp jeder Komponenteninstanz, sodass jede Eigenschaft, die auf dem Komponentenobjekt definiert ist, als Eigenschaft von vnode.state zugänglich ist. Dies ermöglicht eine einfache "Blueprint"-State-Initialisierung.

Im folgenden Beispiel wird data zu einer Eigenschaft des vnode.state-Objekts der ComponentWithInitialState-Komponente.

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

m(ComponentWithInitialState);

// Äquivalentes HTML
// <div>Initial content</div>

Über vnode.state ​

Wie man sehen kann, kann auf den State auch über die Eigenschaft vnode.state zugegriffen werden, die für alle Lebenszyklusmethoden sowie die view-Methode einer Komponente verfügbar ist.

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

// Äquivalentes HTML
// <div>Hello</div>

Über das this-Keyword ​

Auf den State kann auch über das this-Keyword zugegriffen werden, das für alle Lebenszyklusmethoden sowie die view-Methode einer Komponente verfügbar ist.

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

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

// Äquivalentes HTML
// <div>Hello</div>

Beachte, dass der Wert von this in verschachtelten anonymen Funktionen bei Verwendung von ES5-Funktionen nicht die Komponenteninstanz ist. Es gibt zwei empfohlene Möglichkeiten, diese JavaScript-Einschränkung zu umgehen: Verwende Pfeilfunktionen oder, falls diese nicht unterstützt werden, verwende vnode.state.

Klassen ​

Wenn es den Bedürfnissen entspricht (wie in objektorientierten Projekten), können Komponenten auch mit Klassen geschrieben werden:

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

Klassenkomponenten müssen eine view()-Methode definieren, die über .prototype.view erkannt wird, damit der Baum gerendert wird.

Sie können auf die gleiche Weise wie reguläre Komponenten verwendet werden.

javascript
// BEISPIEL: über m.render
m.render(document.body, m(ClassComponent));

// BEISPIEL: über m.mount
m.mount(document.body, ClassComponent);

// BEISPIEL: über m.route
m.route(document.body, '/', {
  '/': ClassComponent,
});

// BEISPIEL: Komponentenzusammensetzung
class AnotherClassComponent {
  view() {
    return m('main', [m(ClassComponent)]);
  }
}

Klassenkomponenten-State ​

Mit Klassen kann der State durch Eigenschaften und Methoden der Klasseninstanz verwaltet und über this aufgerufen werden:

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

Beachte, dass wir Pfeilfunktionen für die Event-Handler-Rückrufe verwenden müssen, damit der this-Kontext korrekt referenziert werden kann.

Mischen von Komponententypen ​

Komponenten können frei gemischt werden. Eine Klassenkomponente kann Closure- oder POJO-Komponenten als Kinder haben usw.

Spezielle Attribute ​

Mithril.js legt spezielle Semantik auf mehrere Property-Keys, daher sollte man es normalerweise vermeiden, diese in normalen Komponentenattributen zu verwenden.

  • Lebenszyklusmethoden: oninit, oncreate, onbeforeupdate, onupdate, onbeforeremove und onremove
  • key, das verwendet wird, um die Identität in Keyed Fragments zu verfolgen
  • tag, das verwendet wird, um VNodes von normalen Attributobjekten und anderen Dingen zu unterscheiden, die keine VNode-Objekte sind.

Vermeide Anti-Patterns ​

Obwohl Mithril.js flexibel ist, werden einige Codemuster nicht empfohlen:

Vermeide aufgeblähte Komponenten ​

Im Allgemeinen ist eine "aufgeblähte Komponente" eine Komponente, die benutzerdefinierte Instanzmethoden hat. Mit anderen Worten, man sollte vermeiden, Funktionen an vnode.state oder this anzuhängen. Es ist äußerst selten, dass es Logik gibt, die logisch in eine Komponenteninstanzmethode passt und die nicht von anderen Komponenten wiederverwendet werden kann. Es ist relativ häufig, dass diese Logik möglicherweise von einer anderen Komponente in der Zukunft benötigt wird.

Es ist einfacher, Code zu refaktorisieren, wenn diese Logik in der Datenebene platziert wird, als wenn sie an den State einer Komponente gebunden ist.

Betrachte diese aufgeblähte Komponente:

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

Normalerweise existiert im Kontext einer größeren Anwendung eine Login-Komponente wie die obige neben Komponenten für die Benutzerregistrierung und die Passwortwiederherstellung. Stell dir vor, wir möchten das E-Mail-Feld beim Navigieren vom Login-Bildschirm zu den Registrierungs- oder Passwortwiederherstellungsbildschirmen (oder umgekehrt) vorab ausfüllen können, sodass der Benutzer seine E-Mail nicht erneut eingeben muss, wenn er versehentlich die falsche Seite ausgefüllt hat (oder vielleicht möchtest du den Benutzer zum Registrierungsformular weiterleiten, wenn ein Benutzername nicht gefunden wird).

Wir sehen sofort, dass das Teilen der Felder username und password von dieser Komponente mit einer anderen schwierig ist. Dies liegt daran, dass die aufgeblähte Komponente ihren State kapselt, was es definitionsgemäß schwierig macht, von außen auf diesen State zuzugreifen.

Es ist sinnvoller, diese Komponente umzugestalten und den State-Code aus der Komponente in die Datenebene der Anwendung zu verschieben. Dies kann so einfach sein wie das Erstellen eines neuen Moduls:

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

Dann können wir die Komponente bereinigen:

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

Auf diese Weise ist das Auth-Modul jetzt die zentrale Datenquelle für den Auth-bezogenen State, und eine Register-Komponente kann leicht auf diese Daten zugreifen und bei Bedarf sogar Methoden wie canSubmit wiederverwenden. Wenn außerdem Validierungscode erforderlich ist (z. B. für das E-Mail-Feld), muss man nur setEmail ändern, und diese Änderung führt die E-Mail-Validierung für jede Komponente durch, die ein E-Mail-Feld ändert.

Als Bonus ist zu beachten, dass wir .bind nicht mehr verwenden müssen, um einen Verweis auf den State für die Event-Handler der Komponente zu behalten.

Leite vnode.attrs nicht direkt an andere vnodes weiter ​

Manchmal ist es wünschenswert, eine Schnittstelle flexibel zu halten und die Implementierung zu vereinfachen, indem Attribute an eine bestimmte Kindkomponente oder ein Element weitergeleitet werden, wie beispielsweise bei Bootstrap's Modal. Es könnte verlockend sein, die Attribute eines vnodes wie folgt weiterzuleiten:

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

Wenn man es wie oben macht, könnte man bei der Verwendung auf Probleme stoßen:

javascript
var MyModal = {
  view: function () {
    return m(
      Modal,
      {
        // Dies schaltet es zweimal um, sodass es nicht angezeigt wird
        onupdate: function (vnode) {
          if (toggle) $(vnode.dom).modal('toggle');
        },
      },
      [
        // ...
      ]
    );
  },
};

Stattdessen sollte man einzelne Attribute in vnodes weiterleiten:

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

// Beispiel
var MyModal = {
  view: function () {
    return m(Modal, {
      attrs: {
        // Dies schaltet es einmal um
        onupdate: function (vnode) {
          if (toggle) $(vnode.dom).modal('toggle');
        },
      },
      // ...
    });
  },
};

Manipuliere nicht children ​

Wenn eine Komponente eine bestimmte Vorstellung davon hat, wie sie Attribute oder Kindelemente verwendet, sollte man auf benutzerdefinierte Attribute umsteigen.

Oft ist es wünschenswert, mehrere Sätze von Kindelementen zu definieren, z. B. wenn eine Komponente einen konfigurierbaren Titel und einen Textkörper hat.

Vermeide die Destrukturierung der Eigenschaft children für diesen Zweck.

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

// umständlicher Anwendungsfall
m(Header, [
  [m('h1', 'My title'), m('small', 'A small note')],
  m('h2', 'Lorem ipsum'),
]);

Die obige Komponente bricht die Annahme, dass Kindelemente im gleichen zusammenhängenden Format ausgegeben werden, in dem sie empfangen werden. Es ist schwierig, die Komponente zu verstehen, ohne ihre Implementierung zu lesen. Verwende stattdessen Attribute als benannte Parameter und reserviere children für einheitliche Kindinhalte:

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

// klarerer Anwendungsfall
m(BetterHeader, {
  title: [m('h1', 'My title'), m('small', 'A small note')],
  tagline: m('h2', 'Lorem ipsum'),
});

Definiere Komponenten statisch, rufe sie dynamisch auf ​

Vermeide das Erstellen von Komponentendefinitionen innerhalb von Views ​

Wenn man eine Komponente innerhalb einer view-Methode erstellt (entweder direkt inline oder durch Aufrufen einer Funktion, die dies tut), hat jedes Redraw einen anderen Klon der Komponente. Beim Vergleich von Komponenten-VNodes wird davon ausgegangen, dass zwei Komponenten unterschiedlich sind, selbst wenn sie letztendlich äquivalenten Code ausführen, falls die Komponente, auf die der neue VNode verweist, nicht strikt mit der Komponente übereinstimmt, auf die der alte VNode verweist. Dies bedeutet, dass Komponenten, die dynamisch über eine Fabrik erstellt werden, immer neu erstellt werden.

Aus diesem Grund sollte man vermeiden, Komponenten neu zu erstellen. Verwende stattdessen Komponenten idiomatisch.

javascript
// VERMEIDEN
var ComponentFactory = function (greeting) {
  // erstellt bei jedem Aufruf eine neue Komponente
  return {
    view: function () {
      return m('div', greeting);
    },
  };
};
m.render(document.body, m(ComponentFactory('hello')));
// Das zweite Aufrufen erstellt div von Grund auf neu, anstatt nichts zu tun
m.render(document.body, m(ComponentFactory('hello')));

// BEVORZUGEN
var Component = {
  view: function (vnode) {
    return m('div', vnode.attrs.greeting);
  },
};
m.render(document.body, m(Component, { greeting: 'hello' }));
// Das zweite Aufrufen ändert das DOM nicht
m.render(document.body, m(Component, { greeting: 'hello' }));

Vermeide das Erstellen von Komponenteninstanzen außerhalb von Views ​

Umgekehrt gilt aus ähnlichen Gründen: Wenn eine Komponenteninstanz außerhalb einer View erstellt wird, führen zukünftige Redraws eine Gleichheitsprüfung des Knotens durch und überspringen ihn. Daher sollten Komponenteninstanzen immer innerhalb von Views erstellt werden:

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

Im obigen Beispiel erhöht das Klicken auf die Schaltfläche der Zählerkomponente den State-Zähler, aber seine View wird nicht ausgelöst, da der VNode, der die Komponente darstellt, denselben Verweis hat und der Renderprozess sie daher nicht vergleicht. Man sollte Komponenten immer in der View aufrufen, um sicherzustellen, dass ein neuer VNode erstellt wird:

javascript
// BEVORZUGEN
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
Vorherige SeiteVirtuelle DOM Knoten
Nächste SeiteLebenszyklus-Methoden

Veröffentlicht unter der MIT-Lizenz.

Copyright (c) 2024 Mithril Contributors

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

Veröffentlicht unter der MIT-Lizenz.

Copyright (c) 2024 Mithril Contributors