Olá, meu nome é ... Cara. O número de mãos é 2. O número de pernas é 2. O tipo sanguíneo é 1. Rhesus é verdadeiro.
Pode parecer que somente com essas informações, sem nome, sobrenome ou até apelido, é difícil me distinguir de muitos outros autores de artigos. E você estará certo. No entanto, no front-end, muitas vezes vejo como o nome do elemento é substituído por sua descrição. E ninguém se importa.

Sente-se, uma jornada fascinante espera por você à frente de problemas sérios de projetos sérios, que, no entanto, costumam ser subestimados.
Testes
Imagine-se como um escritor de testes automáticos de ponta a ponta. Você precisa testar se, por exemplo, no Yandex, no canto superior direito, o nome correto do usuário conectado é exibido.

Dê esta página a um designer de layout típico e ele fornecerá algo como isto:
<body> <aside> <div class="card"> <div class="username">admin</div> </div> </aside> <section> </section> </body>
Pergunta de preenchimento: como encontrar o elemento da casa em que o nome de usuário é exibido?

Aqui, o testador pode escolher entre duas cadeiras:
- Escreva um seletor css ou xpath do formulário ao
aside > .card > .username
e ore para que nenhum outro cartão apareça na barra lateral. E nenhum outro nome de usuário apareceu no cartão. E para que ninguém o tenha alterado para nenhum botão. E ele não o embrulhou em nenhuma tomada. Em resumo, este é um seletor muito frágil, cuja utilização interromperá os testes com as menores alterações na página. - Peça ao desenvolvedor para adicionar um identificador exclusivo para a página . Este é o caminho certo para provocar a raiva do desenvolvedor. Afinal, ele tem tudo sobre os componentes. E os componentes são exibidos muito onde, e não sabem (e não deveriam saber) nada sobre o aplicativo. É ingênuo acreditar que qualquer componente estará sempre na página em uma única cópia, o que significa que o identificador não pode ser costurado no próprio componente. Mas no nível do aplicativo, há apenas o uso do componente LegoSidebar. E jogar identificadores através de vários níveis de aninhamento de componentes também é uma oportunidade.
Como você pode ver, uma opção é pior que a outra - desenvolvedores ou testadores sofrem. Além disso, como regra, um viés em relação a este último. Porque eles entram nos negócios como regra, quando o primeiro já concluiu o desenvolvimento desse recurso e estão cortando o outro com força e principal.
Vamos ver como os designers de layout da Yandex lidaram com o layout deste bloco simples (tive que remover 90% do lixo para que a essência ficasse visível):
<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>
Graças ao BEM, o elemento que precisamos contém informações no nome da classe sobre o contexto em um nível (algum tipo de nome de usuário em algum cartão). Mas você pode ter certeza de que esse cartão estará sempre sozinho na página? Uma suposição ousada. Então, novamente, você tem que escolher entre dois bancos.

E aqui está outro exemplo com um campo de pesquisa em que o desenvolvedor foi forçado a colocar um identificador. Bem, ele colocou:
<input class="input__control input__input" id="text" name="text" />
O desenvolvedor é ruim? Não, arquitetura incorreta que não ajuda a gerar identificadores globalmente exclusivos.
Estatísticas
Imagine-se como analista. Você precisa entender por que os usuários costumam clicar para abrir o menu da conta na barra lateral do Yandex : por nome de usuário ou por foto do perfil.

Você precisa de informações agora, para ter apenas uma bancada - trabalhar com as estatísticas que já foram coletadas. Mesmo que os desenvolvedores tenham certeza de que todos os cliques em todas as páginas já foram gravados, preservando toda a hierarquia de elementos no momento do clique, você ainda não pode escolher o seletor certo para filtrar os cliques necessários. E mesmo que você peça ao desenvolvedor para fazer isso, ele também provavelmente cometerá algum erro. Especialmente se o layout mudou com o tempo.
Ou seja, você precisa de identificadores exclusivos globais para os elementos necessários. Além disso, aposta muito bem com antecedência. Mas como a maioria dos desenvolvedores lida mal com previsões precisas do futuro, geralmente fica assim: o analista procura os desenvolvedores e solicita o identificador e recebe as informações no melhor dos casos em um mês.

No caso do Yandex, o fruto dos esforços heróicos dos desenvolvedores é assim:
<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, e como seria legal para qualquer elemento sempre ter esses identificadores. Para que eles também sejam compreensíveis para o homem. E, ao mesmo tempo, eram estáveis e não mudavam com nenhuma alteração no layout. Mas com transmissão manual, a felicidade não pode ser alcançada.
Estilos
Imagine-se como um designer de layout. Você precisa estilizar especialmente o nome de usuário no cartão na barra lateral Yandex. Faremos a primeira abordagem ao projétil, sem usar o BEM. Aqui está o seu componente:
const Morda = ()=> <div class="morda"> {} <LegoSidebar /> </div>
E aqui está um pacote de componentes suportados por caras 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>
Em suma, dá o seguinte resultado:
<body class="morda"> <aside class="lego-sidebar"> <div class="lego-card"> <div class="lego-username">admin</div> </div> </aside> </body>

Se o isolamento de estilos for usado, você simplesmente não terá nada para sentar. Aguarde e espere que esses outros caras adicionem uma classe personalizada aos seus componentes através do LegoSidebar, no Lego
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>
E é bom que eles sejam incorporados diretamente um no outro, e não através de uma dúzia de componentes intermediários. Caso contrário, eles morrerão sob a carga de macarrão de copiar e colar.

Se o isolamento não for utilizado, seja bem-vindo a uma cadeira feita de seletores frágeis:
.morda .lego-sidebar > .lego-card > .lego-username:first-letter { color : inherit; }
No entanto, se tivéssemos uma ferramenta que usasse nomes locais:
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>
Eu os juntaria levando em consideração o aninhamento de componentes, gerando classes até a raiz do aplicativo:
<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>
Em seguida, poderíamos estilizar qualquer elemento , por mais profundo que fosse:
.morda_sidebar_username:first-letter { color : inherit; }
Não, é fantástico. Isso não acontece.

Transferência de itens
Imagine-se como um desenvolvedor de uma biblioteca de renderização. Algoritmos reativos de alto desempenho usando VirtualDOM, IncrementalDOM, DOM Batching e outro WhateverDOM permitem gerar esse tipo de DOM para uma placa de scrum em apenas alguns segundos:
<div class="dashboard"> <div class="column-todo"> <div class="task"> </div> </div> <div class="column-wip"> </div> <div class="column-done"> </div> </div>
Deste tipo de estado:
{ todo : [ { }, ] , wip : [] , done : [] , }
E aqui está a má sorte: o usuário começa a arrastar e soltar tarefas para frente e para trás e espera que isso aconteça rapidamente. Parece que você só precisa pegar o elemento DOM da tarefa e movê-lo para outro local no DOM-e. Mas você precisará trabalhar manualmente com o DOM e garantir que em todos os lugares as tarefas sejam sempre renderizadas exatamente na mesma árvore do DOM, o que geralmente não é o caso - geralmente há pequenas diferenças. Em resumo, alterar o DOM manualmente é como sentar em um monociclo: um movimento inadvertido e nada o salvará da gravidade. É necessário, de alguma forma, explicar o sistema de renderização para que ele entenda onde a tarefa foi transferida e onde uma foi excluída e a outra foi adicionada.

Para resolver esse problema, é necessário equipar as visualizações com identificadores. Se os identificadores corresponderem, a exibição renderizada poderá simplesmente ser movida para um novo local. Se eles não corresponderem, essas são entidades diferentes e você precisará destruir uma e criar outra. É importante que os identificadores não sejam repetidos e não possam coincidir por acaso.
Enquanto você transfere elementos dentro do mesmo elemento DOM pai, o atributo-chave de React , o parâmetro ngForTrackBy do Angular e coisas semelhantes em outras estruturas podem ajudá-lo. Mas estas são decisões muito particulares. Vale a pena mover a tarefa para outra coluna, e todas essas otimizações param de funcionar.
Mas se cada elemento DOM tivesse um identificador globalmente exclusivo, independentemente de onde esse elemento fosse renderizado, o uso de getElementById
reutilizaria rapidamente a árvore DOM existente ao mover uma entidade de um lugar para outro. Diferentemente das muletas para renderizar listas mencionadas acima, os identificadores globais resolvem o problema sistematicamente e não quebram, mesmo que os caracteres agrupados ou outro jogo apareçam nas colunas:
<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
Imagine-se como um designer de layout. E você precisa adicionar uma div
por lá. Apresentado? Agora se mate. Você já está corrompido pelo html.
Na verdade, você não precisa adicionar uma div
, mas um bloco para o nome do cartão. title
- o nome desse bloco, refletindo sua semântica no local de uso. div
é um tipo de bloco que reflete sua aparência e comportamento, independentemente de onde é usado. Se estivéssemos digitando no TypeScript, ele seria expresso assim:
const Title : DIV
Podemos criar instantaneamente uma instância do tipo:
const Title : DIV = new DIV({ children : [ taskName ] })
E deixe o script de hora imprimir o tipo automaticamente:
const Title = new DIV({ children : [ taskName ] })
Bem, aqui, mesmo o HTML não está longe:
const Title = <div>{ taskName }</div>
Observe que Title
não é apenas um nome de variável aleatória que é usado e jogado fora. Essa é a semântica primária desse elemento. E, para não perdê-lo, deve ser refletido como resultado de:
const Title = <div id="title">{ taskName }</div>
E novamente nos livramos da tautologia:
<div id="title">{ taskName }</div>
Adicione os elementos restantes:
<div id="task"> <div id="title">{ taskName }</div> <div id="deadline">{ taskDue }</div> <div id="description">{ taskDescription }</div> </div>
Observe que, além do título do cartão de tarefas, pode haver muitos outros cabeçalhos, incluindo os cabeçalhos de outras tarefas:
<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>
Assim, para cada elemento são formados identificadores legíveis por humanos que refletem com precisão sua semântica e, portanto, são globalmente únicos.
Observe que a semântica é determinada pela associação, não pela localização. Embora o cartão de tarefas /dashboard/task=fh5yfp6e
esteja na coluna /dashboard/todo
, ele pertence ao /dashboard
. Foi ele quem o criou. Ele configurou. Ele deu um nome a ela e garantiu a exclusividade de seu identificador. Ele controla completamente. Ele a destruirá.

Mas o uso de "tags html corretas" não é semântica, é tipificação:
<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>
Preste atenção a duas marcas de time
com semântica completamente diferente.
Localização
Imagine-se como um tradutor. Você tem uma tarefa extremamente simples - traduzir uma linha de texto do inglês para o russo. Você obteve a sequência "Concluído". Se essa ação for necessária, você precisará traduzir como "Concluído" e, se o estado, como "Concluído", mas se esse for o estado da tarefa, "Concluído". Sem informações sobre o contexto de uso, é impossível traduzir corretamente o texto. E aqui o desenvolvedor novamente tem duas lojas:
- Forneça textos com comentários com informações de contexto. E os comentários são com preguiça de escrever. E quão completa é a informação necessária não está clara. E mais código já é obtido do que ao receber textos por chave.
- Receba textos por chave, depois de ler o que você pode entender o contexto de uso. Definido manualmente, ele não garante a integridade das informações, mas pelo menos será único.

Mas e se não queremos, de qualquer maneira, como nos sentar, mas queremos nos orgulhar de uma base firme das melhores práticas? Então, precisamos usar uma combinação do nome do tipo (componente, modelo), o nome local do elemento nesse tipo e o nome de sua propriedade como chave. Para o texto "Concluído" como o nome da coluna, essa chave seria github_issues_dashboard:column-done:title
. E para o texto "Concluído" no botão de conclusão da tarefa no cartão de tarefas, o identificador já será github_issues_task-card:button-done:label
). Esses, é claro, não são os identificadores dos quais falamos anteriormente, mas essas chaves são formadas com os mesmos nomes que explicitamente ou implicitamente atribuímos aos elementos constituintes. Se os nomearmos explicitamente, teremos a oportunidade de automatizar a geração de várias chaves e identificadores. Mas, se implicitamente, você deve definir essas chaves e identificadores manualmente e esperar que a confusão com o nome da mesma entidade em lugares diferentes e de maneiras diferentes não ocorra.
Depuração
Imagine-se como um desenvolvedor de aplicativos. Um relatório de bug chega até você:
! ! , : Uncaught TypeError: f is not a function at <Button> at <Panel> at <App>
"Sim, aquele mesmo famoso f
de algum botão, em algum painel", dizia o especialista em sofás, "Tudo está claro."

Ou não? E se fosse apenas um identificador exclusivo:
/morda/sidebar/close
- Sim, o botão fechar da barra lateral está quebrado. VasyaPas, isto é para você, eles mudaram os rolos.
Vasya se senta para um anexo, direciona o identificador recebido para o console de desenvolvimento e recebe uma instância do componente, onde fica imediatamente claro que alguém inteligente passou a linha para o botão como manipulador para pressionar:

É bom que cada componente tenha um identificador. E por esse identificador, é fácil obter esse componente , sem precisar dançar no depurador. É verdade que precisamos de uma ferramenta que permita encontrar um componente por identificador. Mas e se o próprio identificador for o código do programa para obter o componente?
<button id="Components['/morda/sidebar/close']">X</button>
Em seguida, esse código pode ser copiado diretamente no console para acesso rápido ao estado do componente, não importa quantas vezes recarregemos a página e alteramos o código.
O que fazer
Se você usa $ mol, não precisa fazer nada - apenas sente-se e faça uma massagem vibratória na íntegra:
$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
Um programador simplesmente não pode falhar sintaticamente em dar um nome exclusivo a um componente. O seguinte DOM é gerado a partir desta descrição do 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>
O código nos identificadores não é apenas globalmente exclusivo, mas também é uma API através da qual você pode acessar qualquer componente. Bem, stektrays é apenas um conto de fadas:
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

Se você é viciado em React, pode transferir para um transformador JSX customizado para gerar identificadores globalmente exclusivos por nomes locais de elementos incorporados no componente. Você pode fazer o mesmo com a geração de classes para estilizar. Para um exemplo com um painel, os modelos são mais ou menos assim:
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" />
Na saída gerada:
<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>

Se você é refém de qualquer outra estrutura, crie-a para que os autores da questão incluam a capacidade opcional de gerar identificadores e classes. Ou pelo menos para adicionar uma API através da qual esses recursos possam ser implementados independentemente.
Resumindo, lembro por que você precisa atribuir nomes exclusivos a todos os elementos:
- Simplicidade e estabilidade dos testes E2E.
- Facilidade de coletar estatísticas de uso de aplicativos e sua análise.
- Estilo fácil.
- Eficiência de renderização.
- Semântica precisa e abrangente.
- Facilidade de localização.
- Facilidade de depuração.