Bonjour, je m'appelle ... Mec. Le nombre de mains est 2. Le nombre de jambes est 2. Le groupe sanguin est 1. Le rhésus est vrai.
Il peut vous sembler que ce n'est que par cette information, sans nom, nom de famille ou même surnom, qu'il est difficile de me distinguer de nombreux autres auteurs d'articles. Et vous aurez raison. Cependant, à l'avant, je vois souvent comment le nom de l'élément est remplacé par sa description. Et personne ne s'en soucie.

Asseyez-vous, un voyage fascinant vous attend face à de sérieux problèmes de projets sérieux, pourtant souvent sous-estimés.
Les tests
Imaginez-vous en tant que rédacteur de tests automatiques de bout en bout. Vous devez tester que, par exemple, dans Yandex dans le coin supérieur droit, le nom correct de l'utilisateur connecté s'affiche.

Donnez cette page à un concepteur de mise en page typique et il vous donnera quelque chose comme ceci:
<body> <aside> <div class="card"> <div class="username">admin</div> </div> </aside> <section> </section> </body>
Question de renvoi: comment trouver l'élément house où le nom d'utilisateur est affiché?

Ici, le testeur a le choix entre deux chaises:
- Écrivez un sélecteur css ou xpath du formulaire à
aside > .card > .username
et priez pour qu'aucune autre carte n'apparaisse jamais dans la barre latérale. Et aucun autre nom d'utilisateur n'apparaissait sur la carte. Et pour que personne ne l'ait changé en aucun bouton. Et il ne l'a enveloppé dans aucune prise. Bref, il s'agit d'un sélecteur très fragile, dont l'utilisation cassera les tests au moindre changement sur la page. - Demandez au développeur d'ajouter un identifiant unique pour la page . C'est la bonne façon d'engendrer la colère du développeur. Après tout, il a tout sur les composants. Et les composants s'affichent beaucoup là où, et ne savent (et ne devraient pas savoir) quoi que ce soit sur l'application. Il est naïf de croire qu'un composant sera toujours sur la page en une seule copie, ce qui signifie que l'identifiant ne peut pas être cousu dans le composant lui-même. Mais au niveau de l'application, il n'y a que l'utilisation du composant LegoSidebar. Et lancer des identifiants à travers plusieurs niveaux d'imbrication de composants est également une opportunité.
Comme vous pouvez le voir, une option est pire que l'autre - les développeurs ou les testeurs en souffrent. De plus, en règle générale, un biais envers ces derniers. Parce qu'ils entrent dans les affaires en règle générale, lorsque les premiers ont déjà terminé le développement de cette fonctionnalité, et qu'ils coupent l'autre avec puissance et force.
Voyons comment les concepteurs de mise en page de Yandex ont géré la mise en page de ce bloc simple (j'ai dû supprimer 90% des ordures pour que l'essence soit visible):
<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>
Grâce à BEM, l'élément dont nous avons besoin contient des informations dans le nom de classe sur le contexte d'un niveau supérieur (une sorte de nom d'utilisateur dans une carte). Mais pouvez-vous être sûr qu'une telle carte sera toujours seule sur la page? Une hypothèse audacieuse. Donc, encore une fois, vous devez choisir entre deux tabourets.

Et voici un autre exemple avec un champ de recherche où le développeur a été obligé de mettre un identifiant. Eh bien, il a mis:
<input class="input__control input__input" id="text" name="text" />
Le développeur est-il mauvais? Non, mauvaise architecture qui ne permet pas de générer des identificateurs uniques au monde.
Statistiques
Imaginez-vous en tant qu'analyste. Vous devez comprendre pourquoi les utilisateurs cliquent souvent pour ouvrir le menu du compte dans la barre latérale Yandex : par nom d'utilisateur ou par photo de profil.

Vous avez besoin d'informations en ce moment, vous n'avez donc qu'un seul banc - travaillez avec les statistiques qui sont déjà collectées. Même si les développeurs se sont assurés que tous les clics sur toutes les pages étaient déjà enregistrés tout en préservant la hiérarchie complète des éléments au moment du clic, vous ne pouvez toujours pas faire le bon sélecteur pour filtrer les clics nécessaires. Et même si vous demandez au développeur de le faire, lui aussi est susceptible de faire une erreur quelque part. Surtout si la mise en page a changé au fil du temps.
Autrement dit, vous avez besoin d'identificateurs uniques globaux pour les éléments dont vous avez besoin. De plus, apposé très bien à l'avance. Mais comme la majorité des développeurs font face à une mauvaise prévision de l'avenir, cela ressemble généralement à ceci: l'analyste vient vers les développeurs et demande l'identifiant, et reçoit les informations dans le meilleur des cas en un mois.

Dans le cas de Yandex, le fruit des efforts héroïques des développeurs ressemble à ceci:
<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, et comme ce serait cool pour n'importe quel élément de toujours avoir ces identifiants. Pour qu'ils soient également compréhensibles pour l'homme. Et en même temps, ils étaient stables et ne changeaient avec aucun changement de disposition. Mais avec une transmission manuelle, le bonheur ne peut être atteint.
Les styles
Imaginez-vous en tant que concepteur de mise en page. Vous devez spécialement styliser le nom d'utilisateur dans la carte dans la barre latérale Yandex. Nous ferons la première approche du projectile, sans utiliser de BEM. Voici votre composant:
const Morda = ()=> <div class="morda"> {} <LegoSidebar /> </div>
Et voici un ensemble de composants pris en charge par des gars complètement différents:
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>
Dans l'ensemble, on obtient le résultat suivant:
<body class="morda"> <aside class="lego-sidebar"> <div class="lego-card"> <div class="lego-username">admin</div> </div> </aside> </body>

Si l'isolement des styles est utilisé, alors vous n'avez tout simplement rien pour quoi vous asseoir. Levez-vous et attendez que ces autres gars ajoutent une classe personnalisée à leurs composants via LegoSidebar, à LegoUsername:
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>
Et c'est bien s'ils sont intégrés directement les uns dans les autres, et non via une douzaine de composants intermédiaires. Sinon, ils mourront sous la charge de nouilles du copier-coller.

Si l'isolation n'est pas utilisée, alors bienvenue sur une chaise faite de sélecteurs fragiles:
.morda .lego-sidebar > .lego-card > .lego-username:first-letter { color : inherit; }
Cependant, si nous avions un outil qui prendrait les noms locaux:
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>
Je les regrouperais en tenant compte de l'imbrication des composants, en générant des classes jusqu'à la racine de l'application:
<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>
Ensuite, nous pourrions styliser n'importe quel élément , quelle que soit sa profondeur:
.morda_sidebar_username:first-letter { color : inherit; }
Non, c'est fantastique. Cela n'arrive pas.

Transfert d'articles
Imaginez-vous en tant que développeur d'une bibliothèque de rendu. Des algorithmes réactifs hautes performances utilisant VirtualDOM, IncrementalDOM, DOM Batching et autres WwhatDOM vous permettent de générer ce type de DOM pour une Scrum Board en quelques secondes:
<div class="dashboard"> <div class="column-todo"> <div class="task"> </div> </div> <div class="column-wip"> </div> <div class="column-done"> </div> </div>
De ce genre d'état:
{ todo : [ { }, ] , wip : [] , done : [] , }
Et voici la malchance: l'utilisateur commence à faire glisser et déposer des tâches d'avant en arrière et s'attend à ce que cela se produise rapidement. Il semblerait que vous ayez juste besoin de prendre l'élément DOM de la tâche et de le déplacer vers un autre endroit dans le DOM-e. Mais vous devrez travailler manuellement avec le DOM et être sûr que, dans tous les endroits, les tâches sont toujours rendues exactement dans la même arborescence DOM, ce qui n'est souvent pas le cas - il y a généralement des différences mineures. En bref, changer le DOM manuellement, c'est comme s'asseoir sur un monocycle: un mouvement par inadvertance et rien ne vous sauvera de la gravité. Il est nécessaire d'expliquer en quelque sorte le système de rendu afin qu'il comprenne où la tâche a été transférée, où l'un a été supprimé et l'autre a été ajouté.

Pour résoudre ce problème, il est nécessaire d'équiper les vues d'identifiants. Si les identificateurs correspondent, la vue rendue peut simplement être déplacée vers un nouvel emplacement. S'ils ne correspondent pas, ce sont des entités différentes et vous devez en détruire une et en créer une autre. Il est important que les identifiants ne soient pas répétés et ne puissent pas coïncider par hasard.
Pendant que vous transférez des éléments dans le même élément DOM parent, l' attribut clé de React , le paramètre ngForTrackBy de Angular et des choses similaires dans d'autres frameworks peuvent vous aider. Mais ce sont des décisions trop privées. Il vaut la peine de déplacer la tâche vers une autre colonne et toutes ces optimisations cessent de fonctionner.
Mais si chaque élément DOM avait un identificateur global unique, quel que soit l'endroit où cet élément a été rendu, l'utilisation de getElementById
réutiliserait rapidement l'arborescence DOM existante lors du déplacement d'une entité d'un endroit à un autre. Contrairement aux béquilles pour le rendu des listes mentionnées ci-dessus, les identificateurs globaux résolvent systématiquement le problème et ne se cassent pas même si des regroupements ou un autre jeu apparaissent dans les colonnes:
<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>

Sémantique
Imaginez-vous en tant que concepteur de mise en page. Et vous devez ajouter un div
là-bas. Présenté? Maintenant, tuez-vous. Vous êtes déjà corrompu par html.
En fait, vous n'avez pas besoin d'ajouter un div
, mais un bloc de nom de carte. title
- le nom de ce bloc, reflétant sa sémantique dans le lieu d'utilisation. div
est un type de bloc reflétant son apparence et son comportement, quel que soit l'endroit où il est utilisé. Si nous composions sur TypeScript, cela s'exprimerait ainsi:
const Title : DIV
Nous pouvons instantanément créer une instance du type:
const Title : DIV = new DIV({ children : [ taskName ] })
Et laissez le script de temps imprimer le type automatiquement:
const Title = new DIV({ children : [ taskName ] })
Eh bien, ici, même le HTML n'est pas loin:
const Title = <div>{ taskName }</div>
Notez que le Title
n'est pas seulement un nom de variable aléatoire qui est utilisé et jeté. Il s'agit de la sémantique principale de cet élément. Et pour ne pas la perdre, elle doit se refléter à la suite de:
const Title = <div id="title">{ taskName }</div>
Et encore une fois, nous nous débarrassons de la tautologie:
<div id="title">{ taskName }</div>
Ajoutez les éléments restants:
<div id="task"> <div id="title">{ taskName }</div> <div id="deadline">{ taskDue }</div> <div id="description">{ taskDescription }</div> </div>
Notez qu'en plus du titre de la fiche de tâche, il peut y avoir de nombreux autres en-têtes, y compris les en-têtes de carte d'autres tâches:
<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>
Ainsi, pour chaque élément, des identificateurs lisibles par l'homme sont formés qui reflètent avec précision leur sémantique et sont donc globalement uniques.
Veuillez noter que la sémantique est déterminée par l'adhésion et non par l'emplacement. Bien que la carte de tâches /dashboard/task=fh5yfp6e
se trouve dans la colonne /dashboard/task=fh5yfp6e
, elle appartient au /dashboard
. C'est lui qui l'a créé. Il l'a installé. Il lui a donné un nom et a assuré l'unicité de son identifiant. Il le contrôle complètement. Il la détruira.

Mais l'utilisation de "balises html correctes" n'est pas de la sémantique, c'est de la typification:
<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>
Faites attention à deux balises time
avec une sémantique complètement différente.
Localisation
Imaginez-vous en tant que traducteur. Votre tâche est extrêmement simple: traduire une ligne de texte de l'anglais vers le russe. Vous avez obtenu la chaîne "Terminé". Si cette action, alors il est nécessaire de traduire comme "Terminer", et si l'état, alors comme "Terminé", mais si c'est l'état de la tâche, alors "Terminé". Sans informations sur le contexte d'utilisation, il est impossible de traduire correctement le texte. Et ici, le développeur a encore deux boutiques:
- Fournissez des textes avec des commentaires avec des informations contextuelles. Et les commentaires sont trop paresseux pour être écrits. Et la mesure dans laquelle les informations sont nécessaires n'est pas claire. Et plus de code est déjà obtenu que lors de la réception de textes par clé.
- Recevez les textes par clé, après avoir lu ce que vous pouvez comprendre le contexte d'utilisation. Défini manuellement, il ne garantit pas l'exhaustivité des informations, mais au moins il sera unique.

Mais que se passe-t-il si nous ne voulons en aucun cas comment nous asseoir, mais si nous voulons nous tenir fièrement et fermement sur les meilleures pratiques? Ensuite, nous devons utiliser une combinaison du nom du type (composant, modèle), du nom local de l'élément dans ce type et du nom de sa propriété comme clé. Pour le texte "Terminé" comme nom de colonne, cette clé serait github_issues_dashboard:column-done:title
. Et pour le texte "Terminé" sur le bouton d'achèvement de la tâche dans la carte des tâches, l'identifiant sera déjà github_issues_task-card:button-done:label
). Ce ne sont bien sûr pas les identifiants dont nous avons parlé plus haut, mais ces clés sont formées à partir des mêmes noms que nous donnons explicitement ou implicitement aux éléments constitutifs. Si nous les nommons explicitement, nous avons la possibilité d'automatiser la génération de différentes clés et identifiants. Mais si implicitement, vous devez définir ces clés et identificateurs manuellement et espérer que le désordre avec le nom de la même entité à différents endroits de différentes manières ne se produira pas.
Débogage
Imaginez-vous en tant que développeur d'applications. Un rapport de bug vous parvient:
! ! , : Uncaught TypeError: f is not a function at <Button> at <Panel> at <App>
"Ouais, ce même f
célèbre depuis un bouton, sur un panneau", dirait l'expert du canapé, "Tout est clair."

Ou pas? Et s'il ne s'agissait que d'un identifiant unique:
/morda/sidebar/close
- Ouais, le bouton de fermeture de la barre latérale est cassé. VasyaPas, c'est pour vous, ils ont déplacé les rouleaux.
Vasya s'assoit pour une pièce jointe, conduit l'identifiant reçu dans la console de développement, puis il reçoit une instance du composant, où il est immédiatement clair qu'une personne intelligente a transmis la ligne au bouton en tant que gestionnaire de clic:

Il est bon que chaque composant ait un identifiant. Et grâce à cet identifiant, ce composant est facile à obtenir , sans danser autour du débogueur. Certes, nous avons besoin d'un outil qui vous permet de trouver un composant par identifiant. Mais que se passe-t-il si l'identifiant lui-même est un code de programme pour obtenir le composant?
<button id="Components['/morda/sidebar/close']">X</button>
Ensuite, ce code peut être copié directement sur la console pour un accès rapide à l'état du composant, peu importe le nombre de fois que nous rechargeons la page et modifions le code.
Que faire
Si vous utilisez $ mol, vous n'avez rien à faire - il vous suffit de vous asseoir et d'obtenir un massage vibratoire complet:
$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
Un programmeur ne peut tout simplement pas échouer syntaxiquement à donner à un composant un nom unique. Le DOM suivant est généré à partir de cette description de composant:
<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>
Le code dans les identificateurs est non seulement unique au monde, mais est également une API à travers laquelle vous pouvez accéder à n'importe quel composant. Eh bien, stektrays n'est qu'un conte de fées:
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

Si vous êtes accro à React, vous pouvez effectuer un transfert vers un transformateur JSX personnalisé pour générer des identificateurs uniques au niveau mondial par les noms locaux des éléments incorporés dans le composant. Vous pouvez faire de même avec la génération de classes de style. Pour un exemple avec un tableau de bord, les modèles ressemblent à ceci:
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" />
En sortie générant:
<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>

Si vous êtes otage d'un autre cadre, créez-le pour que les auteurs du problème ajoutent la possibilité facultative de générer des identificateurs et des classes. Ou au moins pour ajouter une API à travers laquelle ces fonctionnalités pourraient être implémentées indépendamment.
En résumé, je vous rappelle pourquoi vous devez donner des noms uniques à tous les éléments:
- Simplicité et stabilité des tests E2E.
- Facilité de collecte des statistiques d'utilisation des applications et de leur analyse.
- Style facile.
- Efficacité de rendu.
- Sémantique précise et complète.
- Facilité de localisation.
- Facilité de débogage.