Hola, mi nombre es ... hombre. El número de manos es 2. El número de piernas es 2. El tipo de sangre es 1. Rhesus es cierto.
Puede parecerle que solo con esta información, sin un nombre, apellido o incluso un apodo, es difícil distinguirme de muchos otros autores de artículos. Y tendrás razón. Sin embargo, en el extremo frontal, a menudo veo cómo el nombre del elemento se reemplaza por su descripción. Y a nadie le importa.

Siéntese, le espera un viaje fascinante frente a problemas serios de proyectos serios, que, sin embargo, a menudo se subestiman.
Pruebas
Imagínese como escritor de pruebas automáticas de extremo a extremo. Debe probar que, por ejemplo, en Yandex en la esquina superior derecha se muestra el nombre correcto del usuario conectado.

Dele esta página a un diseñador de diseño típico y él le dará algo como esto:
<body> <aside> <div class="card"> <div class="username">admin</div> </div> </aside> <section> </section> </body>
Pregunta de relleno: ¿cómo encontrar el elemento de la casa donde se muestra el nombre de usuario?

Aquí el probador tiene la opción de dos sillas:
- Escriba un selector css o xpath del formulario a
aside > .card > .username
y ore para que nunca aparezcan otras tarjetas en la barra lateral. Y no aparecieron otros nombres de usuario en la tarjeta. Y para que nadie lo haya cambiado a ningún botón. Y no lo envolvió en ningún enchufe. En resumen, este es un selector muy frágil, cuyo uso romperá las pruebas con los más mínimos cambios en la página. - Pídale al desarrollador que agregue un identificador único para la página . Esta es la forma correcta de generar la ira del desarrollador. Después de todo, él tiene todo sobre los componentes. Y los componentes se muestran mucho donde, y no saben (y no deberían saber) nada sobre la aplicación. Es ingenuo creer que cualquier componente siempre estará en la página en una sola copia, lo que significa que el identificador no se puede coser en el componente mismo. Pero a nivel de aplicación, solo se utiliza el componente LegoSidebar. Y lanzar identificadores a través de varios niveles de anidamiento de componentes también es una oportunidad.
Como puede ver, una opción es peor que la otra: tanto los desarrolladores como los evaluadores sufren. Además, por regla general, un sesgo hacia este último. Debido a que ingresan a los negocios como regla general, cuando los primeros ya han completado el desarrollo de esta característica, y están cortando al otro con gran poder.
Veamos cómo los diseñadores de diseño de Yandex trataron el diseño de este bloque simple (tuve que eliminar el 90% de la basura para que la esencia fuera 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>
Gracias a BEM, el elemento que necesitamos tiene información en el nombre de la clase sobre el contexto de un nivel superior (algún tipo de nombre de usuario en alguna tarjeta). Pero, ¿puede estar seguro de que dicha tarjeta siempre estará sola en la página? Una suposición audaz. Entonces, nuevamente, debes elegir entre dos taburetes.

Y aquí hay otro ejemplo con un campo de búsqueda donde el desarrollador se vio obligado a poner un identificador. Bueno, él puso:
<input class="input__control input__input" id="text" name="text" />
¿Es malo el desarrollador? No, una mala arquitectura que no ayuda a generar identificadores únicos a nivel mundial.
Estadísticas
Imagínate a ti mismo como analista. Debe comprender por qué los usuarios a menudo hacen clic para abrir el menú de la cuenta en la barra lateral de Yandex : por nombre de usuario o por imagen de perfil.

Necesita información en este momento, por lo que solo tiene un banco: trabaje con las estadísticas que ya se han recopilado. Incluso si los desarrolladores se aseguraron de que todos los clics en todas las páginas ya estuvieran registrados mientras preservaban toda la jerarquía de elementos en el momento del clic, aún no puede hacer el selector correcto para filtrar los clics necesarios. E incluso si le pide al desarrollador que haga esto, es probable que él también cometa un error en alguna parte. Especialmente si el diseño ha cambiado con el tiempo.
Es decir, necesita identificadores únicos globales para los elementos que necesita. Por otra parte, pegado con mucha antelación. Pero dado que la mayoría de los desarrolladores se las arreglan mal con una predicción precisa del futuro, generalmente se ve así: el analista acude a los desarrolladores y les pide el identificador, y recibe la información en el mejor de los casos en un mes.

En el caso de Yandex, el fruto de los heroicos esfuerzos de los desarrolladores se ve así:
<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>
Ah, y qué bueno sería para cualquier elemento tener siempre estos identificadores. Para que también sean entendibles por el hombre. Y al mismo tiempo, eran estables y no cambiaron con ningún cambio en el diseño. Pero con la transmisión manual, la felicidad no se puede lograr.
Estilos
Imagínese como diseñador de maquetación. Debe diseñar especialmente el nombre de usuario en la tarjeta en la barra lateral de Yandex. Haremos el primer acercamiento al proyectil, sin usar BEM. Aquí está tu componente:
const Morda = ()=> <div class="morda"> {} <LegoSidebar /> </div>
Y aquí hay un paquete de componentes compatibles con tipos completamente diferentes:
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>
En general, da el siguiente resultado:
<body class="morda"> <aside class="lego-sidebar"> <div class="lego-card"> <div class="lego-username">admin</div> </div> </aside> </body>

Si se utiliza el aislamiento de estilos, simplemente no tiene nada para sentarse. Párate y espera a que estos otros muchachos agreguen una clase personalizada a sus componentes a través de LegoSidebar, a 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>
Y es bueno si están integrados directamente entre sí, y no a través de una docena de componentes intermedios. De lo contrario, morirán bajo la carga de fideos de copiar y pegar.

Si no se usa aislamiento, entonces bienvenido a una silla hecha de selectores frágiles:
.morda .lego-sidebar > .lego-card > .lego-username:first-letter { color : inherit; }
Sin embargo, si tuviéramos una herramienta que tomaría nombres locales:
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>
Los uniría teniendo en cuenta la anidación de componentes, generando clases hasta la raíz de la aplicación:
<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>
Entonces podríamos estilizar cualquier elemento , sin importar cuán profundo pueda ser:
.morda_sidebar_username:first-letter { color : inherit; }
No, es fantastico. Esto no pasa.

Transferencia de artículos
Imagínese como desarrollador de una biblioteca de renderizado. Los algoritmos reactivos de alto rendimiento que usan VirtualDOM, IncrementalDOM, DOM Batching y cualquier otra aplicación EverythingDOM le permiten generar este tipo de DOM para un tablero de scrum en solo unos segundos:
<div class="dashboard"> <div class="column-todo"> <div class="task"> </div> </div> <div class="column-wip"> </div> <div class="column-done"> </div> </div>
De este tipo de estado:
{ todo : [ { }, ] , wip : [] , done : [] , }
Y aquí está la mala suerte: el usuario comienza a arrastrar y soltar tareas de un lado a otro y espera que esto suceda rápidamente. Parece que solo necesita tomar el elemento DOM de la tarea y moverlo a otro lugar en el DOM-e. Pero tendrá que trabajar manualmente con el DOM y asegurarse de que en todos los lugares las tareas siempre se representen exactamente en el mismo árbol DOM, lo que a menudo no es el caso; generalmente hay pequeñas diferencias. En resumen, cambiar el DOM manualmente es como sentarse en un monociclo: un movimiento inadvertido y nada te salvará de la gravedad. Es necesario explicar de alguna manera el sistema de representación para que entienda dónde se transfirió la tarea, y dónde se eliminó uno y se agregó el otro.

Para resolver este problema, es necesario equipar las vistas con identificadores. Si los identificadores coinciden, la vista renderizada simplemente se puede mover a una nueva ubicación. Si no coincidían, estas son entidades diferentes y debes destruir una y crear otra. Es importante que los identificadores no se repitan y no puedan coincidir por casualidad.
Mientras transfiere elementos dentro del mismo elemento DOM principal, el atributo clave de React , el parámetro ngForTrackBy de Angular y cosas similares en otros marcos pueden ayudarlo. Pero estas son decisiones demasiado privadas. Vale la pena mover la tarea a otra columna, y todas estas optimizaciones dejan de funcionar.
Pero si cada elemento DOM tenía un identificador único globalmente, independientemente de dónde se representara este elemento, entonces usar getElementById
reutilizaría rápidamente el árbol DOM existente al mover una entidad de un lugar a otro. A diferencia de las muletas para renderizar las listas mencionadas anteriormente, los identificadores globales resuelven el problema sistemáticamente y no se romperán incluso si aparecen caracteres agrupados o algún otro juego en las columnas:
<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>

Semántica
Imagínese como diseñador de maquetación. Y necesitas agregar un div
allí. Presentado? Ahora mátate a ti mismo. Ya estás dañado por html.
De hecho, no necesita agregar un div
, sino un bloque de nombre de tarjeta. title
: el nombre de este bloque, que refleja su semántica en el lugar de uso. div
es un tipo de bloque que refleja su apariencia y comportamiento, independientemente de dónde se use. Si estuviéramos escribiendo en TypeScript, se expresaría así:
const Title : DIV
Podemos crear instantáneamente una instancia del tipo:
const Title : DIV = new DIV({ children : [ taskName ] })
Y deje que el script de tiempo imprima el tipo automáticamente:
const Title = new DIV({ children : [ taskName ] })
Bueno, aquí, incluso HTML no está lejos:
const Title = <div>{ taskName }</div>
Tenga en cuenta que Title
no es solo un nombre de variable aleatorio que se utiliza y se descarta. Esta es la semántica primaria de este elemento. Y para no perderlo, debe reflejarse como resultado de:
const Title = <div id="title">{ taskName }</div>
Y nuevamente nos deshacemos de la tautología:
<div id="title">{ taskName }</div>
Agregue los elementos restantes:
<div id="task"> <div id="title">{ taskName }</div> <div id="deadline">{ taskDue }</div> <div id="description">{ taskDescription }</div> </div>
Tenga en cuenta que, además del título de la tarjeta de tareas, puede haber muchos otros encabezados, incluidos los encabezados de otras tareas:
<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>
Por lo tanto, para cada elemento se forman identificadores legibles por humanos que reflejan con precisión su semántica y, por lo tanto, son globalmente únicos.
Tenga en cuenta que la semántica está determinada por la membresía, no por la ubicación. Aunque la tarjeta de tareas /dashboard/task=fh5yfp6e
se encuentra en la columna /dashboard/todo
, pertenece al /dashboard
. Fue él quien lo creó. El lo preparó. Él le dio un nombre y aseguró la singularidad de su identificador. Él lo controla completamente. El la destruirá.

Pero el uso de "etiquetas html correctas" no es semántica, es tipificación:
<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>
Presta atención a las dos etiquetas de time
con una semántica completamente diferente.
Localización
Imagínate a ti mismo como traductor. Tiene una tarea extremadamente simple: traducir una línea de texto del inglés al ruso. Tienes la cadena "Hecho". Si esta acción, entonces es necesario traducir como "Finalizar", y si es el estado, entonces como "Finalizado", pero si este es el estado de la tarea, entonces "Completar". Sin información sobre el contexto de uso, es imposible traducir correctamente el texto. Y aquí el desarrollador nuevamente tiene dos tiendas:
- Proporcione textos con comentarios con información de contexto. Y los comentarios son demasiado flojos para escribir. Y cuán completa se necesita la información no está claro. Y ya se obtiene más código que cuando se reciben textos por clave.
- Reciba textos por clave, habiendo leído lo que puede entender el contexto de uso. Establecido manualmente, no garantiza la integridad de la información, pero al menos será única.

Pero, ¿qué sucede si de todos modos no queremos sentarnos, pero queremos estar orgullosos de las mejores prácticas? Luego, necesitamos usar una combinación del nombre del tipo (componente, plantilla), el nombre local del elemento dentro de este tipo y el nombre de su propiedad como clave. Para el texto "Listo" como el nombre de la columna, esta clave sería github_issues_dashboard:column-done:title
. Y para el texto "Listo" en el botón de finalización de tarea en la tarjeta de tarea, el identificador ya será github_issues_task-card:button-done:label
). Estos, por supuesto, no son los identificadores de los que hablamos anteriormente, pero estas claves se forman a partir de los mismos nombres que damos explícita o implícitamente a los elementos constitutivos. Si los nombramos explícitamente, tenemos la oportunidad de automatizar la generación de varias claves e identificadores. Pero si está implícito, debe configurar estas claves e identificadores manualmente y esperar que el desorden con el nombre de la misma entidad en diferentes lugares de diferentes maneras no se rompa.
Depuración
Imagínese como desarrollador de aplicaciones. Te llega un informe de error:
! ! , : Uncaught TypeError: f is not a function at <Button> at <Panel> at <App>
"Sí, esa misma f
famosa de algún botón, en algún panel", decía el experto en sofás, "Todo está claro".

O no? Y si fuera solo un identificador único:
/morda/sidebar/close
- Sí, el botón de cierre de la barra lateral está roto. VasyaPas, esto es para ti, movieron los rollos.
Vasya se sienta para un archivo adjunto, conduce el identificador recibido a la consola de desarrollo y luego recibe una instancia del componente, donde queda claro de inmediato que alguien inteligente pasó la línea al botón como un controlador de clic:

Es bueno que cada componente tenga un identificador. Y con este identificador, este componente es fácil de obtener , sin tener que bailar alrededor del depurador. Es cierto que necesitamos una herramienta que le permita encontrar un componente por identificador. Pero, ¿qué pasa si el identificador en sí mismo es el código del programa para obtener el componente?
<button id="Components['/morda/sidebar/close']">X</button>
Luego, este código se puede copiar directamente a la consola para acceder rápidamente al estado del componente, sin importar cuántas veces volvamos a cargar la página y cambiemos el código.
Que hacer
Si usa $ mol, entonces no necesita hacer nada, solo siéntese y reciba un masaje vibratorio completo:
$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 programador simplemente no puede fallar sintácticamente al darle un nombre único a un componente. El siguiente DOM se genera a partir de esta descripción del componente:
<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>
El código en los identificadores no solo es globalmente único, sino que también es una API a través de la cual puede acceder a cualquier componente. Bueno, stektrays es solo un cuento de hadas:
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 está enganchado a React, puede transferirlo a un transformador JSX personalizado para generar identificadores únicos a nivel mundial mediante nombres locales de elementos integrados en el componente. Puedes hacer lo mismo con la generación de clases para estilizar. Para un ejemplo con un tablero, las plantillas se ven así:
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 la salida generando:
<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 es rehén de cualquier otro marco, créelo para que los autores del problema agreguen la capacidad opcional de generar identificadores y clases. O al menos para agregar una API a través de la cual dichas características podrían implementarse de forma independiente.
Resumiendo, le recuerdo por qué necesita dar nombres únicos a todos los elementos:
- Simplicidad y estabilidad de las pruebas E2E.
- Facilidad de recopilar estadísticas de uso de aplicaciones y su análisis.
- Estilo fácil.
- Eficiencia de renderizado.
- Semántica precisa y completa.
- Facilidad de localización.
- Facilidad de depuración.