Hallo, ich heiße ... Mann. Die Anzahl der Hände ist 2. Die Anzahl der Beine ist 2. Die Blutgruppe ist 1. Rhesus ist wahr.
Es mag Ihnen so erscheinen, als ob es nur durch diese Informationen ohne Vor- oder Nachnamen oder sogar ohne Spitznamen schwierig ist, mich von vielen anderen Artikelautoren zu unterscheiden. Und du wirst recht haben. Im Frontend sehe ich jedoch oft, wie der Name des Elements durch seine Beschreibung ersetzt wird. Und niemand kümmert sich darum.

Nehmen Sie Platz, eine faszinierende Reise erwartet Sie vor ernsthaften Problemen ernsthafter Projekte, die jedoch oft unterschätzt werden.
Tests
Stellen Sie sich vor, Sie schreiben automatische End-to-End-Tests. Sie müssen testen, ob beispielsweise in Yandex in der oberen rechten Ecke der korrekte Name des angemeldeten Benutzers angezeigt wird.

Geben Sie diese Seite einem typischen Layout-Designer und er wird Ihnen so etwas geben:
<body> <aside> <div class="card"> <div class="username">admin</div> </div> </aside> <section> </section> </body>
Backfill-Frage: Wie finde ich das Hauselement, in dem der Benutzername angezeigt wird?

Hier hat der Tester die Wahl zwischen zwei Stühlen:
- Schreiben Sie einen CSS- oder Xpath-Selektor des Formulars
aside > .card > .username
und beten Sie, dass keine anderen Karten jemals in der Seitenleiste erscheinen. Und keine anderen Benutzernamen erschienen auf der Karte. Und damit niemand es in irgendeinen Knopf geändert hat. Und er wickelte es nicht in eine Steckdose. Kurz gesagt, dies ist ein sehr fragiler Selektor, dessen Verwendung die Tests bei den geringsten Änderungen auf der Seite unterbricht. - Bitten Sie den Entwickler, eine eindeutige Kennung für die Seite hinzuzufügen . Dies ist der richtige Weg, um den Ärger des Entwicklers auf sich zu ziehen. Immerhin hat er alles an den Komponenten. Und die Komponenten werden häufig angezeigt und wissen nichts über die Anwendung (und sollten es auch nicht wissen). Es ist naiv zu glauben, dass sich eine Komponente immer in einer einzigen Kopie auf der Seite befindet, was bedeutet, dass die Kennung nicht in die Komponente selbst eingenäht werden kann. Auf Anwendungsebene wird jedoch nur die LegoSidebar-Komponente verwendet. Das Werfen von Bezeichnern durch mehrere Ebenen der Komponentenverschachtelung ist ebenfalls eine Gelegenheit.
Wie Sie sehen, ist eine Option schlechter als eine andere - entweder Entwickler oder Tester leiden darunter. Darüber hinaus in der Regel eine Tendenz zu letzterem. Weil sie in der Regel in das Geschäft eintreten, wenn die ersten die Entwicklung dieser Funktion bereits abgeschlossen haben und die anderen mit Macht und Kraft abschneiden.
Mal sehen, wie Layout-Designer von Yandex mit dem Layout dieses einfachen Blocks umgegangen sind (ich musste 90% des Mülls entfernen, damit die Essenz sichtbar wurde):
<div class="desk-notif-card"> <div class="desk-notif-card__card"> <div class="desk-notif-card__domik-user usermenu-link"> <a class="home-link usermenu-link__control" href="https://passport.yandex.ru"> <span class="username desk-notif-card__user-name">admin</span> </a> </div> </div> </div>
Dank BEM enthält das Element, das wir benötigen, Informationen im Klassennamen über den Kontext auf einer Ebene höher (eine Art Benutzername auf einer Karte). Aber können Sie sicher sein, dass eine solche Karte immer alleine auf der Seite ist? Eine kühne Annahme. Sie müssen also wieder zwischen zwei Hockern wählen.

Und hier ist ein weiteres Beispiel mit einem Suchfeld, in dem der Entwickler gezwungen war, eine Kennung einzugeben. Nun, er sagte:
<input class="input__control input__input" id="text" name="text" />
Ist der Entwickler schlecht? Nein, schlechte Architektur , die nicht dazu beiträgt, global eindeutige Bezeichner zu generieren.
Statistiken
Stellen Sie sich als Analyst vor. Sie müssen verstehen, warum Benutzer häufig auf klicken, um das Kontomenü in der Yandex- Seitenleiste zu öffnen: nach Benutzername oder nach Profilbild.

Sie benötigen jetzt Informationen, haben also nur eine Bank - arbeiten Sie mit den bereits gesammelten Statistiken. Selbst wenn die Entwickler sichergestellt haben, dass alle Klicks auf allen Seiten bereits aufgezeichnet wurden, während die gesamte Hierarchie der Elemente zum Zeitpunkt des Klicks beibehalten wurde, können Sie nicht die richtige Auswahl treffen, um die erforderlichen Klicks herauszufiltern. Und selbst wenn Sie den Entwickler dazu auffordern, wird wahrscheinlich auch er irgendwo einen Fehler machen. Besonders wenn sich das Layout im Laufe der Zeit geändert hat.
Das heißt, Sie benötigen globale eindeutige Bezeichner für die Elemente, die Sie benötigen. Darüber hinaus sehr weit im Voraus angebracht. Da die Mehrheit der Entwickler mit einer genauen Vorhersage der Zukunft nur schlecht zurechtkommt, sieht es normalerweise so aus: Der Analyst kommt zu den Entwicklern und fragt nach der Kennung und erhält die Informationen im besten Fall eines Monats.

Im Fall von Yandex sieht die Frucht der heldenhaften Bemühungen der Entwickler folgendermaßen aus:
<div class="desk-notif-card__domik-user usermenu-link"> <a class="home-link usermenu-link__control" href="https://passport.yandex.ru" data-statlog="notifications.mail.login.usermenu.toggle" > </a> <a class="home-link desk-notif-card__user-icon-link usermenu-link__control avatar" href="https://passport.yandex.ru" data-statlog="notifications.mail.login.usermenu.toggle-icon" > </a> </div>
Oh, und wie cool wäre es für jedes Element, immer diese Bezeichner zu haben. Damit sie auch für den Menschen verständlich sind. Gleichzeitig waren sie stabil und änderten sich bei keiner Änderung des Layouts. Aber mit Schaltgetriebe kann Glück nicht erreicht werden.
Stile
Stellen Sie sich als Layouter vor. Sie müssen den Benutzernamen auf der Karte in der Yandex-Seitenleiste besonders formatieren. Wir werden den ersten Ansatz für das Projektil machen, ohne BEM zu verwenden. Hier ist Ihre Komponente:
const Morda = ()=> <div class="morda"> {} <LegoSidebar /> </div>
Und hier ist ein Bündel von Komponenten, die von ganz anderen Leuten unterstützt werden:
const LegoSidebar = ( { username } )=> <aside className="lego-sidebar"> <LegoCard> <LegoUsername>{ username }</LegoUsername> </LegoCard> </aside> const LegoCard = ( {} , ... children )=> <div className="lego-card"> { ... children } </div> const LegoUsername = ( {} , ... children )=> <div className="lego-username"> { ... children } </div>
Alles in allem ergibt sich folgendes Ergebnis:
<body class="morda"> <aside class="lego-sidebar"> <div class="lego-card"> <div class="lego-username">admin</div> </div> </aside> </body>

Wenn die Isolierung von Stilen verwendet wird, haben Sie einfach nichts, wofür Sie sich setzen müssen. Stehen Sie und warten Sie, bis diese anderen Jungs ihren Komponenten über LegoSidebar eine benutzerdefinierte Klasse zu LegoUsername hinzugefügt haben:
const LegoSidebar = ( { username , rootClass , cardClass , usernameClass } )=> <aside className={ "lego-sidebar " + rootClass }> <LegoCard rootClass={ cardClass }> <LegoUsername rootClass={ usernameClass}>{ username }</LegoUsername> </LegoCard> </aside> const LegoCard = ( { rootClass } , ... children )=> <div className={ "lego-card " + rootClass }> { ... children } </div> const LegoUsername = ( { rootClass } , ... children )=> <div className={ "lego-username " + rootClass }> { ... children } </div>
Und es ist gut, wenn sie direkt ineinander eingebettet sind und nicht durch ein Dutzend Zwischenkomponenten. Andernfalls sterben sie unter der Last von Nudeln aus Copy-Paste.

Wenn keine Isolierung verwendet wird, begrüßen Sie einen Stuhl aus zerbrechlichen Selektoren:
.morda .lego-sidebar > .lego-card > .lego-username:first-letter { color : inherit; }
Wenn wir jedoch ein Tool hätten, das lokale Namen annehmen würde:
const Morda = ()=> <div> {} <Lego_sidebar id="sidebar" /> </div> const Lego_sidebar = ( { username } )=> <aside> <Lego_card id="profile"> <Lego_username id="username">{ username }</Lego_username> </Lego_card> </aside> const Lego_card = ( {} , ... children )=> <div> { ... children } </div> const Lego_username = ( {} , ... children )=> <div> { ... children } </div>
Ich würde sie unter Berücksichtigung der Verschachtelung von Komponenten zusammenfügen und Klassen bis zum Stammverzeichnis der Anwendung generieren:
<body class="morda"> <aside class="lego_sidebar morda_sidebar"> <div class="lego_card lego_sidebar_profile morda_sidebar_profile"> <div class="lego_username lego_sidebar_username morda_sidebar_username">admin</div> </div> </aside> </body>
Dann könnten wir jedes Element stilisieren , egal wie tief es sein mag:
.morda_sidebar_username:first-letter { color : inherit; }
Nein, es ist fantastisch. Das passiert nicht.

Artikelübertragung
Stellen Sie sich vor, Sie sind Entwickler einer Rendering-Bibliothek. Mit leistungsstarken reaktiven Algorithmen, die VirtualDOM, IncrementalDOM, DOM Batching und anderes WhateverDOM verwenden, können Sie diese Art von DOM für ein Scrum Board in nur wenigen Sekunden generieren:
<div class="dashboard"> <div class="column-todo"> <div class="task"> </div> </div> <div class="column-wip"> </div> <div class="column-done"> </div> </div>
Aus dieser Art von Staat:
{ todo : [ { }, ] , wip : [] , done : [] , }
Und hier ist das Pech: Der Benutzer beginnt, Aufgaben hin und her zu ziehen und abzulegen, und erwartet, dass dies schnell geschieht. Es scheint, dass Sie nur das DOM-Element der Aufgabe nehmen und an eine andere Stelle im DOM-e verschieben müssen. Sie müssen jedoch manuell mit dem DOM arbeiten und sicherstellen, dass Aufgaben an allen Stellen immer in genau demselben DOM-Baum gerendert werden, was häufig nicht der Fall ist - es gibt normalerweise geringfügige Unterschiede. Kurz gesagt, das manuelle Ändern des DOM ist wie das Sitzen auf einem Einrad: Eine unbeabsichtigte Bewegung und nichts rettet Sie vor der Schwerkraft. Es ist notwendig, das Rendering-System irgendwie zu erklären, damit es versteht, wo die Aufgabe übertragen wurde und wo eine gelöscht und die andere hinzugefügt wurde.

Um dieses Problem zu lösen, müssen die Ansichten mit Bezeichnern ausgestattet werden. Wenn die Bezeichner übereinstimmen, kann die gerenderte Ansicht einfach an einen neuen Speicherort verschoben werden. Wenn sie nicht übereinstimmen, handelt es sich um verschiedene Entitäten, und Sie müssen eine zerstören und eine andere erstellen. Es ist wichtig, dass die Bezeichner nicht wiederholt werden und nicht zufällig zusammenfallen können.
Während Sie Elemente innerhalb desselben übergeordneten DOM-Elements übertragen, können Ihnen das Schlüsselattribut von React , der Parameter ngForTrackBy von Angular und ähnliche Elemente in anderen Frameworks helfen. Dies sind jedoch zu private Entscheidungen. Es lohnt sich, die Aufgabe in eine andere Spalte zu verschieben, und all diese Optimierungen funktionieren nicht mehr.
Wenn jedoch jedes DOM-Element eine global eindeutige Kennung hätte, unabhängig davon, wo dieses Element gerendert wurde, würde die Verwendung von getElementById
den vorhandenen DOM-Baum schnell wiederverwenden, wenn eine Entität von einem Ort an einen anderen getElementById
. Im Gegensatz zu den oben erwähnten Krücken zum Rendern von Listen lösen globale Bezeichner das Problem systematisch und brechen nicht einmal, wenn gruppierte Charaktere oder ein anderes Spiel in den Spalten erscheinen:
<div id="/dashboard"> <div id="/dashboard/column-todo"> <div id="/dashboard/todo/priority=critical"> <div id="/dashboard/task=y43uy4t6"> </div> </div> </div> <div id="/dashboard/column-wip"> <div id="/dashboard/wip/assignee=jin"></div> </div> <div id="/dashboard/column-done"> <div id="/dashboard/done/release=0.9.9"></div> </div> </div>

Semantik
Stellen Sie sich als Layouter vor. Und du musst dort drüben ein div
hinzufügen. Präsentiert? Jetzt bring dich um. Sie sind bereits durch HTML beschädigt.
Tatsächlich müssen Sie kein div
, sondern einen Block für den Namen der Karte hinzufügen. title
- Der Name dieses Blocks, der seine Semantik am Verwendungsort widerspiegelt. div
ist eine Art Block, der sein Aussehen und Verhalten widerspiegelt, unabhängig davon, wo er verwendet wird. Wenn wir TypeScript setzen würden, würde dies folgendermaßen ausgedrückt:
const Title : DIV
Wir können sofort eine Instanz des Typs erstellen:
const Title : DIV = new DIV({ children : [ taskName ] })
Lassen Sie das Zeitskript den Typ automatisch drucken:
const Title = new DIV({ children : [ taskName ] })
Hier ist sogar HTML nicht weit:
const Title = <div>{ taskName }</div>
Beachten Sie, dass Title
nicht nur ein zufälliger Variablenname ist, der verwendet und weggeworfen wird. Dies ist die primäre Semantik dieses Elements. Und um es nicht zu verlieren, muss es reflektiert werden als Ergebnis von:
const Title = <div id="title">{ taskName }</div>
Und wieder werden wir die Tautologie los:
<div id="title">{ taskName }</div>
Fügen Sie die restlichen Elemente hinzu:
<div id="task"> <div id="title">{ taskName }</div> <div id="deadline">{ taskDue }</div> <div id="description">{ taskDescription }</div> </div>
Beachten Sie, dass es neben dem Titel der Aufgabenkarte viele andere Überschriften geben kann, einschließlich der Kartenüberschriften anderer Aufgaben:
<div id="/dashboar/column-todo"> <div id="/dashboard/column-todo/title">To Do</div> <div id="/dashboard/task=fh5yfp6e"> <div id="/dashboard/task=fh5yfp6e/title">{ taskName }</div> <div id="/dashboard/task=fh5yfp6e/deadline">{ taskDue }</div> <div id="/dashboard/task=fh5yfp6e/description">{ taskDescription }</div> </div> <div id="/dashboard/task=fhty50or"> <div id="/dashboard/task=fhty50or/title">{ taskName }</div> <div id="/dashboard/task=fhty50or/deadline">{ taskDue }</div> <div id="/dashboard/task=fhty50or/description">{ taskDescription }</div> </div> </div>
Somit werden für jedes Element vom Menschen lesbare Bezeichner gebildet, die ihre Semantik genau widerspiegeln und daher global eindeutig sind.
Bitte beachten Sie, dass die Semantik von der Mitgliedschaft und nicht vom Standort abhängt. Obwohl sich die Aufgabenkarte /dashboard/task=fh5yfp6e
in der Spalte /dashboard/todo
, gehört sie zum /dashboard
. Er hat es geschaffen. Er hat es eingerichtet. Er gab ihr einen Namen und stellte die Einzigartigkeit ihrer Kennung sicher. Er kontrolliert es vollständig. Er wird sie zerstören.

Die Verwendung von "korrekten HTML-Tags" ist jedoch keine Semantik, sondern eine Typisierung:
<section id="/dashboard/column-todo"> <h4 id="/dashboard/column-todo/title">To Do</h4> <figure id="/dashboard/task=fh5yfp6e"> <h5 id="/dashboard/task=fh5yfp6e/title">{ taskName }</h5> <time id="/dashboard/task=fh5yfp6e/created">{ taskCreated }</time> <time id="/dashboard/task=fh5yfp6e/deadline">{ taskDue }</time> <div id="/dashboard/task=fh5yfp6e/description">{ taskDescription }</div> </figure> </section>
Achten Sie auf zwei Zeitmarken mit völlig unterschiedlicher Semantik.
Lokalisierung
Stellen Sie sich als Übersetzer vor. Sie haben eine äußerst einfache Aufgabe: eine Textzeile aus dem Englischen ins Russische zu übersetzen. Du hast die Zeichenfolge "Fertig". Wenn diese Aktion ausgeführt wird, muss sie als "Fertig stellen" und der Status als "Fertig" übersetzt werden. Wenn dies jedoch der Status der Aufgabe ist, wird "Abgeschlossen" angezeigt. Ohne Informationen zum Verwendungskontext ist es nicht möglich, den Text korrekt zu übersetzen. Und hier hat der Entwickler wieder zwei Läden:
- Versehen Sie Texte mit Kommentaren mit Kontextinformationen. Und Kommentare sind zu faul zum Schreiben. Und wie vollständig die Informationen benötigt werden, ist nicht klar. Und es wird bereits mehr Code erhalten als beim Empfang von Texten per Schlüssel.
- Erhalten Sie Texte per Schlüssel, nachdem Sie gelesen haben, dass Sie den Verwendungskontext verstehen können. Manuell festgelegt, garantiert es nicht die Vollständigkeit der Informationen, aber es wird zumindest einzigartig sein.

Aber was ist, wenn wir nicht wollen, wie wir sitzen sollen, sondern stolz auf einer festen Basis von Best Practices stehen wollen? Dann müssen wir eine Kombination aus dem Namen des Typs (Komponente, Vorlage), dem lokalen Namen des Elements in diesem Typ und dem Namen seiner Eigenschaft als Schlüssel verwenden. Für den Text "Fertig" als Spaltenname wäre dieser Schlüssel github_issues_dashboard:column-done:title
. Und für den Text "Fertig" auf der Schaltfläche "Aufgabenabschluss" auf der Aufgabenkarte github_issues_task-card:button-done:label
die Kennung bereits " github_issues_task-card:button-done:label
. Dies sind natürlich nicht die Bezeichner, über die wir zuvor gesprochen haben, aber diese Schlüssel werden aus denselben Namen gebildet, die wir den Bestandteilen explizit oder implizit geben. Wenn wir sie explizit benennen, haben wir die Möglichkeit, die Generierung verschiedener Schlüssel und Bezeichner zu automatisieren. Wenn dies jedoch implizit der Fall ist, müssen Sie diese Schlüssel und Bezeichner manuell festlegen und hoffen, dass das Durcheinander mit dem Namen derselben Entität an verschiedenen Orten auf unterschiedliche Weise nicht ausbricht.
Debuggen
Stellen Sie sich als Anwendungsentwickler vor. Ein Fehlerbericht kommt bei Ihnen an:
! ! , : Uncaught TypeError: f is not a function at <Button> at <Panel> at <App>
"Ja, das gleiche berühmte f
von einem Knopf auf einer Tafel", sagte der Sofa-Experte, "alles ist klar."

Oder nicht? Und wenn es nur eine eindeutige Kennung wäre:
/morda/sidebar/close
- Ja, die Schaltfläche zum Schließen der Seitenleiste ist defekt. VasyaPas, das ist für dich, sie haben die Brötchen bewegt.
Vasya setzt sich für einen Anhang hin, treibt die empfangene Kennung in die Entwicklungskonsole und erhält dann eine Instanz der Komponente, in der sofort klar ist, dass jemand Smart die Zeile als Klick-Handler an die Schaltfläche übergeben hat:

Es ist gut, dass jede Komponente eine Kennung hat. Und durch diese Kennung ist diese Komponente leicht zu bekommen , ohne um den Debugger herum zu tanzen. Wir benötigen ein Tool, mit dem Sie eine Komponente anhand ihrer Kennung finden können. Was aber, wenn der Bezeichner selbst Programmcode ist, um die Komponente zu erhalten?
<button id="Components['/morda/sidebar/close']">X</button>
Dieser Code kann dann direkt in die Konsole kopiert werden, um schnell auf den Status der Komponente zuzugreifen, unabhängig davon, wie oft wir die Seite neu laden und den Code ändern.
Was zu tun ist?
Wenn Sie $ mol verwenden, müssen Sie nichts tun - setzen Sie sich einfach hin und lassen Sie sich vollständig vibrieren:
$ya_morda $mol_view sub / <= Sidebar $ya_lego_sidebar $ya_lego_sidebar $mol_view sub / <= Profile $ya_lego_card sub / <= Username $ya_lego_username sub / <= username \ $ya_lego_card $mol_view $ya_lego_username $mol_view
Ein Programmierer kann es einfach nicht versäumen, einer Komponente einen eindeutigen Namen zu geben. Das folgende DOM wird aus dieser Komponentenbeschreibung generiert:
<body id="$ya_morda.Root(0)" ya_morda mol_view > <ya_lego_sidebar id="$ya_morda.Root(0).Sidebar()" ya_lego_sidebar mol_view ya_morda_sidebar > <ya_lego_card id="$ya_morda.Root(0).Sidebar().Profile()" ya_lego_card mol_view ya_lego_sidebar_profile ya_morda_sidebar_profile > <ya_lego_username id="$ya_morda.Root(0).Sidebar().Username()" ya_lego_username mol_view ya_lego_sidebar_username ya_morda_sidebar_username > admin </ya_lego_username> </ya_lego_card> </ya_lego_sidebar> </body>
Der Code in Bezeichnern ist nicht nur global eindeutig, sondern auch eine API, über die Sie auf jede Komponente zugreifen können. Stektrays sind nur ein Märchen:
Uncaught (in promise) Error: Test error at $mol_state_local.value("mol-todos-85").calculate at $mol_state_local.value("mol-todos-85").pull at $mol_state_local.value("mol-todos-85").update at $mol_state_local.value("mol-todos-85").get at $mol_app_todomvc.Root(0).task at $mol_app_todomvc.Root(0).task_title at $mol_app_todomvc.Root(0).task_title(85).calculate at $mol_app_todomvc.Root(0).task_title(85).pull at $mol_app_todomvc.Root(0).task_title(85).update at $mol_app_todomvc.Root(0).task_title(85).get at $mol_app_todomvc.Root(0).Task_row(85).title at $mol_app_todomvc.Root(0).Task_row(85).Title().value at $mol_app_todomvc.Root(0).Task_row(85).Title().event_change

Wenn Sie von React abhängig sind, können Sie auf einen benutzerdefinierten JSX-Transformator übertragen , um global eindeutige Bezeichner anhand lokaler Namen der in die Komponente eingebetteten Elemente zu generieren. Dasselbe können Sie mit der Generierung von Klassen für das Styling tun. In einem Beispiel mit einem Dashboard sehen die Vorlagen ungefähr so aus:
const Dashboard = ()=> ( <div> <Column id="/column-todo" title="To Do"> <Task id="/task=fh5yfp6e" title="foobar" deadline="yesterday" content="Do it fast!" /> </Column> <Column id="/column-wip" title="WIP" /> <Column id="/column-done" title="Done" /> </div> ) const Column = ( { title } , ... tasks )=> ( <div> <div id="/title">{ title }</div> { tasks } </div> ) const Task = ({ title , deadline , description })=> ( <div> <div id="/title">{ title }</div> <div id="/deadline">{ deadline }</div> <div id="/description">{ description }</div> </div> ) const App = ()=> <Dashboard id="/dashboard" />
Am Ausgang erzeugen:
<div id="/dashboar"> <div id="/dashboar/column-todo"> <div id="/dashboard/column-todo/title">To Do</div> <div id="/dashboard/task=fh5yfp6e"> <div id="/dashboard/task=fh5yfp6e/title">foobar</div> <div id="/dashboard/task=fh5yfp6e/deadline">yesterday</div> <div id="/dashboard/task=fh5yfp6e/description">Do it fast!</div> </div> </div> <div id="/dashboar/wip"> <div id="/dashboard/column-wip/title">WIP</div> </div> <div id="/dashboar/done"> <div id="/dashboard/column-done/title">Done</div> </div> </div>

Wenn Sie Geisel eines anderen Frameworks sind, erstellen Sie es für die Autoren des Problems, um die optionale Möglichkeit zum Generieren von Bezeichnern und Klassen hinzuzufügen. Oder zumindest um eine API hinzuzufügen, über die solche Funktionen unabhängig implementiert werden können.
Zusammenfassend erinnere ich Sie daran, warum Sie allen Elementen eindeutige Namen geben müssen:
- Einfachheit und Stabilität von E2E-Tests.
- Einfache Erfassung von Anwendungsnutzungsstatistiken und deren Analyse.
- Einfaches Styling.
- Effizienz beim Rendern.
- Genaue und umfassende Semantik.
- Einfache Lokalisierung.
- Einfaches Debuggen.