Hello, my name is ... Man. The number of hands is 2. The number of legs is 2. Blood type is 1. Rhesus is true.
It may seem to you that only by this information, without a name, surname or even a nickname, it is difficult to distinguish me from many other article authors. And you will be right. However, in the front end, I often see how the name of the element is replaced by its description. And nobody cares.
Have a seat, a fascinating journey awaits you in front of serious problems of serious projects, which, however, are often underestimated.
Imagine yourself as a writer of automatic end-to-end tests. You need to test that, for example, in Yandex in the upper right corner the correct name of the logged in user is displayed.
Give this page to a typical typesetter and he will give you something like this:
<body> <aside> <div class="card"> <div class="username">admin</div> </div> <!-- - --> </aside> <section> <!-- - --> </section> </body>
Backfill question: how to find the house-element where the username is displayed?
Here the tester has a choice of two chairs:
aside > .card > .username
and pray that no other cards will ever appear in the sidebar. And no other user names appeared in the card. And so that no one changes it to any button. And he did not wrap it in any socket. In short, this is a very fragile selector, the use of which will break the tests at the slightest changes on the page.As you can see, one option is worse than the other - either developers or testers suffer. Moreover, as a rule, a bias towards the latter. Because they enter the business as a rule, when the first have already completed the development of this feature, and they are cutting the other with might and main.
Let's see how layout designers from Yandex dealt with the layout of this simple block (I had to remove 90% of the garbage so that the essence was 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> <!-- - -->
Thanks to BEM, the element we need has information on the context 1 level up in the class name (some username in some card). But can you be sure that such a card will always be alone on the page? A bold assumption. So, again, you have to choose between two stools.
And here is another example with a search field where the developer was forced to put an identifier. Well, he put:
<input class="input__control input__input" id="text" name="text" />
Is the developer bad? No, bad architecture that does not help generate globally unique identifiers.
Imagine yourself as an analyst. You need to understand why users often click to open the account menu in the Yandex sidebar: by user name or by profile picture.
You need information right now, so you have only one bench - to work with the statistics that are already collected. Even if the developers have taken care that all clicks on all pages are already recorded while preserving the entire hierarchy of elements at the time of the click, you still can’t make the right selector to filter out the necessary clicks. And even if you ask the developer to do this, he, too, is likely to make a mistake somewhere. Especially if the layout has changed over time.
That is, you need global unique identifiers for the elements you need. Moreover, affixed very well in advance. But since the majority of developers cope poorly with accurate prediction of the future, it usually looks like this: the analyst comes to the developers and asks for the identifier, and receives the information in the best case in a month.
In the case of Yandex, the fruit of the heroic efforts of the developers looks like this:
<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, and how cool it would be for any element to always have these identifiers. So that they are also understandable to man. And at the same time they were stable, and did not change with any change in layout. But with manual transmission, happiness cannot be achieved.
Imagine yourself as a layout designer. You need to specialize in styling the username in the card in the Yandex sidebar. We will make the first approach to the projectile, without using BEM. Here is your component:
const Morda = ()=> <div class="morda"> {/* - */} <LegoSidebar /> </div>
And here is a bundle of components supported by completely different guys:
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>
All in total gives the following result:
<body class="morda"> <aside class="lego-sidebar"> <div class="lego-card"> <div class="lego-username">admin</div> </div> <!-- - --> </aside> </body>
If isolation of styles is used, then you simply have nothing to sit down for. Stand and wait for these other guys to add a custom class to their components through LegoSidebar, to 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>
And it is good if they are embedded directly in each other, and not through a dozen intermediate components. Otherwise, they will die under a load of noodles from copy-paste.
If insulation is not used, then welcome to a chair made of fragile selectors:
.morda .lego-sidebar > .lego-card > .lego-username:first-letter { color : inherit; }
However, if we had a tool that would take local names:
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>
I would stick them together taking into account the nesting of components, generating classes right down to the root of the 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>
Then we could stylize any element , no matter how deep it may be:
.morda_sidebar_username:first-letter { color : inherit; }
No, it's fantastic. This does not happen.
Imagine yourself as a developer of a rendering library. High-performance reactive algorithms using VirtualDOM, IncrementalDOM, DOM Batching and other WhateverDOM allow you to generate this kind of DOM for a scrum board in just a few seconds:
<div class="dashboard"> <div class="column-todo"> <div class="task"> <!-- html --> </div> <!-- --> </div> <div class="column-wip"> </div> <div class="column-done"> </div> </div>
From this type of state:
{ todo : [ { /* */ }, /* */ ] , wip : [] , done : [] , }
And here's the bad luck: the user begins to dragging tasks back and forth and expects that this will happen quickly. It would seem that you just need to take the DOM element of the task and move it to another place in the DOM-e. But you will have to manually work with the DOM and be sure that in all places tasks are always rendered into exactly the same DOM tree, which is often not the case - there are usually minor differences. In short, changing the DOM manually is like sitting on a unicycle: one inadvertent movement and nothing will save you from gravity. It is necessary to somehow explain the rendering system so that it understands where the task was transferred, and where one was deleted and the other was added.
To solve this problem, it is necessary to equip the views with identifiers. If the identifiers match, then the rendered view can simply be moved to a new location. If they did not match, then these are different entities and you need to destroy one and create another. It is important that the identifiers are not repeated, and could not coincide by chance.
While you are transferring elements within the same parent DOM element, the key attribute from React , the ngForTrackBy parameter from Angular, and similar things in other frameworks can help you. But these are too private decisions. It is worth moving the task to another column, and all these optimizations stop working.
But if each DOM element had a globally unique identifier, regardless of where this element was rendered, then using getElementById
would quickly reuse the existing DOM tree when moving an entity from one place to another. Unlike the crutches for rendering lists mentioned above, global identifiers solve the problem systematically and will not break even if grouped characters or some other game appear in the columns:
<div id="/dashboard"> <div id="/dashboard/column-todo"> <div id="/dashboard/todo/priority=critical"> <div id="/dashboard/task=y43uy4t6"> <!-- html --> </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>
Imagine yourself as a layout designer. And you need to add a div
over there. Presented? Now kill yourself. You are already spoiled by html.
In fact, you do not need to add a div
, but a card name block. title
- the name of this block, reflecting its semantics in the place of use. div
is a type of block that reflects its appearance and behavior, regardless of where it is used. If we were typesetting on TypeScript, then it would be expressed like this:
const Title : DIV
We can instantly create an instance of the type:
const Title : DIV = new DIV({ children : [ taskName ] })
And let the time script print the type automatically:
const Title = new DIV({ children : [ taskName ] })
Well, here, and not far from HTML:
const Title = <div>{ taskName }</div>
Note that Title
is not just a random variable name that is used and thrown away. This is the primary semantics of this element. And in order not to lose it, it must be reflected as a result of:
const Title = <div id="title">{ taskName }</div>
And again we get rid of the tautology:
<div id="title">{ taskName }</div>
Add the remaining elements:
<div id="task"> <div id="title">{ taskName }</div> <div id="deadline">{ taskDue }</div> <div id="description">{ taskDescription }</div> </div>
Note that in addition to the title of the task card, there can be many other headers, including the card headers of other tasks:
<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>
Thus, for each element human-readable identifiers are formed that accurately reflect their semantics and therefore are globally unique.
Please note that semantics are determined by membership, not location. Although the task card /dashboard/task=fh5yfp6e
is located in the /dashboard/todo
column, it belongs to the /dashboard
. It was he who created it. He set it up. He gave her a name and ensured the uniqueness of her identifier. He completely controls it. He will destroy her.
But the use of "correct html tags" is not semantics, it is 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>
Pay attention to two time
tags with completely different semantics.
Imagine yourself as a translator. You have an extremely simple task - to translate a line of text from English into Russian. You got the string "Done". If this action, then it is necessary to translate as "Finish", and if the state, then as "Finished", but if this is the state of the task, then "Complete". Without information about the context of use, it is impossible to correctly translate the text. And here the developer again has two shops:
But what if we do not want anyhow how to sit, but want to stand proudly on a firm basis from best practices? Then we need to use a combination of the name of the type (component, template), the local name of the element within this type, and the name of its property as a key. For the text "Done" as the column name, this key would be github_issues_dashboard:column-done:title
. And for the text "Done" on the task completion button in the task card, the identifier will already be github_issues_task-card:button-done:label
). These, of course, are not the identifiers that we talked about earlier, but these keys are formed from the same names that we explicitly or implicitly give to the constituent elements. If we name them explicitly, then we have the opportunity to automate the generation of various keys and identifiers. But if implicitly, then you have to set these keys and identifiers manually and hope that the mess with the name of the same entity in different places in different ways does not break out.
Imagine yourself as an application developer. A bug report arrives to you:
! ! , : Uncaught TypeError: f is not a function at <Button> at <Panel> at <App>
“Yeah, that same famous f
from some button, on some panel,” the sofa expert would say, “Everything is clear.”
Or not? And if it were just one unique identifier:
/morda/sidebar/close
- Yeah, the sidebar close button is broken. VasyaPas, this is for you, they moved the rolls.
Vasya sits down for the app, drives the received identifier into the development console, and then he receives an instance of the component, where it is immediately clear that someone smart passed the line to the button as a click handler:
It’s good that each component has an identifier. And by this identifier, this component is easy to get , without dancing around the debugger. True, we need a tool to find a component by identifier. But what if the identifier itself is program code to get the component?
<button id="Components['/morda/sidebar/close']">X</button>
Then this code can be copied directly to the console for quick access to the state of the component, no matter how many times we reload the page and change the code.
If you use $ mol, then you do not need to do anything - just sit down and get a vibration massage in full:
$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
The programmer simply cannot syntactically fail to give the component a unique name. The following DOM is generated from this component description:
<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>
The code in identifiers is not only globally unique, but also represents an API through which you can access any component. Well, stektrays is just a fairy tale:
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
If you are addicted to React, you can transfer to a custom JSX transformer to generate globally unique identifiers by local names of elements embedded in the component. You can do the same with the generation of classes for styling. For an example with a dashboard, the templates look something like this:
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" />
At the output generating:
<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>
If you are hostage to any other framework, then create it for the authors of the issue to add the optional ability to generate identifiers and classes. Or at least to add an API through which such features could be implemented independently.
Summarizing, I remind you why you need to give unique names to all elements:
Source: https://habr.com/ru/post/461395/
All Articles