分形

您好,我叫...男人。 手的数量为2。腿的数量为2。血型为1。恒河猴为真。


在您看来,仅凭此信息,没有姓名,姓氏甚至昵称,就很难将我与许多其他文章作者区分开。 你会是对的。 但是,在前端,我经常看到如何用元素描述替换元素的名称。 没有人在乎。


三个男人和一个女孩


坐下来,迷人的旅程在重大项目的严重问题面前等待着您,但是,这些问题常常被低估了。


测验


想象一下自己是自动端对端测试的作者。 您需要测试,例如,在右上角的Yandex中,是否显示了已登录用户的正确名称。


Yandex通过管理员的口吻


将此页面交给典型的布局设计师,他将为您提供以下信息:


<body> <aside> <div class="card"> <div class="username">admin</div> </div> <!-- -  --> </aside> <section> <!-- -  --> </section> </body> 

回填问题:如何找到显示用户名的房屋元素?


两种方式,但本质是一种


测试人员可以选择以下两种椅子:


  1. 编写一个形式为aside > .card > .username 的css或xpath选择器,并祈祷没有其他卡出现在侧栏中。 卡上没有其他用户名。 因此,没有人将其更改为任何按钮。 而且他没有将其包装在任何插座中。 简而言之,这是一个非常脆弱的选择器,使用该选择器将使页面上的任何更改都无法通过测试。
  2. 要求开发人员为页面添加唯一标识符 。 这是引起开发人员愤怒的正确方法。 毕竟,他拥有所有组件。 组件显示在很多地方,并且不知道(也不应该知道)有关应用程序的任何信息。 幼稚地相信任何组件都将始终在页面上并在一个副本中,这意味着无法将标识符缝入组件本身。 但是在应用程序级别,仅使用LegoSidebar组件。 并且通过多个级别的组件嵌套抛出标识符也是一个机会。

如您所见,一个选择比另一个选择更糟-开发人员或测试人员都会遭受损失。 而且,通常,对后者的偏见。 因为他们通常会进入业务,所以当第一个已经完成此功能的开发时,他们就会用力气和主要力量削减其他人。


让我们看看Yandex的布局设计师如何处理这个简单块的布局(我必须删除90%的垃圾,以便使本质可见):


 <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> <!-- -  --> 

多亏了BEM,我们需要的元素在类名称中具有有关上一级上下文的信息(某些卡中的某种用户名)。 但是您可以确定这样的卡片在页面上始终是一个人吗? 一个大胆的假设。 因此,同样,您必须在两个凳子之间进行选择。


哦,多么艰难的选择


这是另一个带有搜索字段的示例 ,其中开发人员被迫放置一个标识符。 好吧,他说:


 <input class="input__control input__input" id="text" name="text" /> 

开发人员不好吗? 不, 糟糕的架构无助于生成全局唯一标识符。


统计资料


想像一下自己是一名分析师。 您需要了解为什么用户经常单击以打开Yandex侧栏中的帐户菜单:按用户名或按个人资料图片。


基本点击面


您现在需要信息,因此只有一个基准-处理已收集的统计信息。 即使开发人员确保在单击时保留所有元素的整个层次结构时,已经记录了所有页面上的所有单击,您仍然不能使用正确的选择器来过滤掉必要的单击。 即使您要求开发人员执行此操作,他也可能在某个地方犯错。 特别是在布局随时间变化的情况下。


也就是说,您需要所需元素的全局唯一标识符。 而且,事先贴得很好。 但是由于大多数开发人员都无法很好地应对未来的准确预测,因此通常看起来像这样:分析师来找开发人员并索要标识符,并在一个月内以最佳情况接收信息。


我觉得我需要一个标识符


对于Yandex,开发人员的英勇努力成果如下:


 <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> 

哦, 任何元素总是拥有这些标识符真是太酷了。 这样他们对人类也是可以理解的。 同时,它们是稳定的,并且在布局上没有任何变化。 但是,通过手动变速箱无法实现幸福。


款式


想象一下自己是一名布局设计师。 您需要在Yandex侧栏中的卡中特别设置用户名的样式。 我们将在不使用BEM的情况下,对射弹进行第一种处理。 这是您的组件:


 const Morda = ()=> <div class="morda"> {/* - */} <LegoSidebar /> </div> 

这是一堆由完全不同的人支持的组件:


 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> 

总而言之,结果如下:


 <body class="morda"> <aside class="lego-sidebar"> <div class="lego-card"> <div class="lego-username">admin</div> </div> <!-- -  --> </aside> </body> 

那里绝对需要缬草


如果使用样式隔离,那么您根本无所事事。 站着等待这些其他人通过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> 

最好是将它们直接嵌入彼此,而不是通过十几个中间组件嵌入。 否则,它们将因复制粘贴而在面条的负载下死亡。


克服自己动手制造的困难


如果不使用绝缘材料,那么欢迎使用由易碎选择器制成的椅子:


 .morda .lego-sidebar > .lego-card > .lego-username:first-letter { color : inherit; } 

但是,如果我们有一个使用本地名称的工具:


 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> 

我会考虑到组件的嵌套将它们放在一起,并生成类,直到应用程序的根目录:


 <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> 

然后,无论元素有多深,我们都可以对其进行样式化


 .morda_sidebar_username:first-letter { color : inherit; } 

不,太好了。 这不会发生。


5年后,只能丢弃99%的时髦代码


项目转移


想象自己是一个渲染库的开发人员。 使用VirtualDOM,IncrementalDOM,DOM Batching和其他WhateverDOM的高性能反应式算法使您可以在几秒钟内为scrum板生成这种DOM:


 <div class="dashboard"> <div class="column-todo"> <div class="task"> <!--  html --> </div> <!--    --> </div> <div class="column-wip"> </div> <div class="column-done"> </div> </div> 

从这种状态:


 { todo : [ { /*   */ }, /*   */ ] , wip : [] , done : [] , } 

不幸的是:用户开始来回拖放任务,并期望这种情况很快发生。 似乎您只需要获取任务的DOM元素并将其移动到DOM-e中的另一个位置。 但是您将不得不手动使用DOM,并确保在所有地方,任务总是始终呈现到完全相同的DOM树中,通常情况并非如此-通常存在细微差异。 简而言之,手动更改DOM就像坐在独轮车上一样:一次无意的移动,没有任何事情可以使您摆脱重力。 有必要以某种方式解释渲染系统,以便它了解任务在何处传输,在何处删除而在另一处添加了任务。


将身份转移到错误的身体时出错


为了解决这个问题,有必要在视图中配备标识符。 如果标识符匹配,则可以将渲染的视图简单地移动到新位置。 如果它们不匹配,则这些是不同的实体,您需要销毁一个实体并创建另一个实体。 重要的是,标识符不要重复,并且不能偶然相符。


在同一父DOM元素中传输元素时,Reactkey属性Angular的ngForTrackBy参数以及其他框架中的类似内容都可以为您提供帮助。 但是这些都是私人决定。 值得将任务移至另一列,所有这些优化都将停止工作。


但是,如果每个DOM元素都有一个全局唯一的标识符,无论该元素在何处呈现,那么在将实体从一个位置移动到另一个位置时,使用getElementById都会快速重用现有的DOM树。 与上述渲染列表的拐杖不同,全局标识符可以系统地解决该问题,即使分组或某些其他游戏出现在列中也不会中断:


 <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> 

分组发言者


语义学


想象一下自己是一名布局设计师。 并且您需要在那添加一个div 。 赠送? 现在自杀。 您已经被html损坏了。


实际上,您不需要添加div ,而需要为卡的名称添加一个块。 title该块的名称,在使用位置反映其语义。 div是一种反映其外观和行为的块,无论在何处使用它。 如果我们在TypeScript上排版,那么它将表示为:


 const Title : DIV 

我们可以立即创建一个类型的实例:


 const Title : DIV = new DIV({ children : [ taskName ] }) 

并让时间脚本自动打印类型:


 const Title = new DIV({ children : [ taskName ] }) 

好吧,在这里,即使HTML也不远:


 const Title = <div>{ taskName }</div> 

请注意, Title不仅仅是使用和丢弃的随机变量名称。 这是该元素的主要语义。 为了不丢失它,它必须反映在以下方面:


 const Title = <div id="title">{ taskName }</div> 

再一次,我们摆脱了重言式:


 <div id="title">{ taskName }</div> 

添加其余元素:


 <div id="task"> <div id="title">{ taskName }</div> <div id="deadline">{ taskDue }</div> <div id="description">{ taskDescription }</div> </div> 

请注意,除了任务卡的标题之外,还可以有许多其他标题,包括其他任务的卡标题:


 <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> 

因此,对于每个元素,形成了人类可读的标识符,这些标识符准确地反映了它们的语义,因此是全局唯一的。


请注意,语义是由成员资格而不是位置决定的。 尽管任务卡/dashboard/task=fh5yfp6e位于/dashboard/todo列中,但它属于/dashboard 。 是他创造了它。 他设置好了。 他给她起了个名字,并确保了标识符的唯一性。 他完全控制了它。 他将摧毁她。


知道你爸爸是谁


但是“正确的html标签”的使用不是语义,而是典型的:


 <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> 

请注意两个语义完全不同的time标签。


本地化


想象一下自己是一名翻译。 您有一个非常简单的任务-将一行文本从英语翻译成俄语。 您得到字符串“ Done”。 如果执行此操作,则有必要将其翻译为“完成”,如果状态为“完成”,则为“完成”,但是如果这是任务的状态,则为“完成”。 没有有关使用上下文的信息,就不可能正确翻译文本。 在这里,开发人员再次拥有两家商店:


  1. 提供带有上下文信息注释的文本。 而且评论太懒了,无法编写。 信息的完整程度尚不清楚。 与通过键接收文本相比,已经获得了更多的代码。
  2. 通过按键接收文本,已阅读您可以理解使用环境的文本。 手动设置并不能保证信息的完整性,但是至少它是唯一的。

再次选择两种邪恶


但是,如果我们不希望如何坐下来,而是想从最佳实践中站稳脚跟,该怎么办? 然后,我们需要使用类型名称(组件,模板),此类型内元素的本地名称及其属性名称的组合作为键。 对于文本“ Done”作为列名,此键将为github_issues_dashboard:column-done:title 。 对于任务卡中任务完成按钮上的文本“完成”,标识符将已经是github_issues_task-card:button-done:label )。 当然,这些不是我们之前提到的标识符,但是这些键是由我们明确或隐式赋予组成元素的相同名称构成的。 如果我们明确命名它们,那么我们将有机会自动生成各种密钥和标识符。 但是,如果是隐式的,则必须手动设置这些键和标识符,并希望不会出现以不同方式在不同位置出现同一实体名称的混乱情况。


侦错


想像一下自己是一名应用程序开发人员。 一个错误报告到达您:


   !  !   ,   : Uncaught TypeError: f is not a function at <Button> at <Panel> at <App> 

沙发专家会说:“是的,在某个面板上,某个按钮上的那个同样出名的f ,一切都清楚。”


在这里和傻瓜


还是不行 并且如果它只是一个唯一标识符:


 /morda/sidebar/close 

-是的,侧边栏关闭按钮坏了。 VasyaPas,这是给你的,他们移动了面包卷。


Vasya坐下来寻找附件,将接收到的标识符驱动到开发控制台中,然后他接收到该组件的实例,在该实例中,很明显有人聪明地将行作为单击处理程序传递给了按钮:


这似乎不是完全的HTML。


每个组件都有一个标识符是很好的。 通过此标识符, 可以轻松获得组件 ,而无需在调试器中跳舞。 的确,我们需要一个工具来允许您通过标识符查找组件。 但是,如果标识符本身就是获取组件的程序代码怎么办?


 <button id="Components['/morda/sidebar/close']">X</button> 

然后,无论我们重新加载页面和更改代码的次数是多少,都可以将该代码直接复制到控制台以快速访问组件的状态。


怎么办


如果您使用$ mol,则无需执行任何操作-只需坐下并充分进行振动按摩即可:


 $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 

程序员在语法上根本无法给组件赋予唯一的名称。 根据此组件描述生成以下DOM:


 <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> 

标识符中的代码不仅是全局唯一的,而且是一个API,您可以通过它访问任何组件。 好吧,stektrays只是一个童话:


 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 

不是那些想让世界变得更美好的人


如果您沉迷于React,则可以转移到自定义JSX转换器,以通过嵌入在组件中的元素的本地名称生成全局唯一标识符。 您可以对样式类的生成进行相同的操作。 对于带有仪表板的示例,模板如下所示:


 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" /> 

在输出生成:


 <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> 

哦,有多少复制粘贴


如果您是任何其他框架的人质,则可以为问题作者创建框架,以添加生成标识符和类的可选功能。 或至少添加一个API,通过该API可以独立实现这些功能。


总而言之,我提醒您为什么需要为所有元素赋予唯一的名称:


  • 端到端测试的简单性和稳定性。
  • 易于收集应用程序使用情况统计信息及其分析。
  • 易于造型。
  • 渲染效率。
  • 准确而全面的语义。
  • 易于本地化。
  • 易于调试。

Source: https://habr.com/ru/post/zh-CN461395/


All Articles