喷气机前端。 我们如何再次重写所有内容的故事

嗨,我是Yandex.Money的Katya。 我继续讲述我如何停止化妆并开始生活。 在第一部分中,我告诉了我如何到达这里以及前端开发人员的工作。 今天-关于前端堆栈,React来自何处以及BEM往何处去。

剧透:BEM没走到任何地方\ _(ツ)_ /¯。 走吧



注意:前端高度集中。 如所承诺的,许多文本,图片和代码。

第2部分。关于技术


2016年末。尝试用React编写,结果可以忍受。 我仍然不怀疑我会在一年内将全部服务转移到React。 2017年开始于Yandex.Money,我有大脑的BEM,但我仍然不怀疑。

我第一次接触Node.js后端


为了熟悉该项目,新开发人员会收到一个测试任务。 我很幸运:我的积压任务已经完成。 在第一天,我就遇到了Node.js。

Yandex.Money的前端不仅负责客户端,还负责作为Node.js应用程序的服务器层。 该应用程序的任务是从Java后端整理数据,以面向视图的形式进行准备,以及服务器渲染和路由。 您会在几年前被告知,我什么都不知道,而且一切都非常简单:当请求从浏览器到服务器时,Node.js为后端生成HTTP请求,接收必要的数据和模板网页。 我们使用Express作为服务器框架,并且为了开发没有旧版链接的内部应用程序,我们决定使用Koa2 。 开发人员喜欢框架的设计,因此我们决定不降级到Express,因此Koa2仍然保留在堆栈中。 但是,我们不会向外部用户推出Koa2代码:该框架没有足够的支持,但是存在开放的漏洞。

我们已经写了关于Node.js在前端中的位置的信息,但是此后发生了一些变化。 Node.js 8已成为LTS,并且已经在我们的生产服务器上运行。 我们还希望放弃Nginx服务器,我们在每台主机上都增加了Nginx服务器来分发静态数据-它们将被Nginx和某天CDN替换为单独的服务器。

为了使项目之间的代码混乱,但又不使其公开,我们使用了一整套工具:将模块存储在Bitbucket中,并将其收集在Jenkins中。 我们还使用了程序包的本地注册表,因此,我们不会进入外部网络-这样可以加快组装速度,并提高整个系统的安全性。 骑行者向我们建议了这种方法,它们很酷。 爱你的支持者;)

我们还进行了一个实验-我们将流程管理器引入了其中一个应用程序,从而简化了Node.js上服务的管理。 他帮助进行了集群,并且还使我们脱离了运行应用程序的旧bash脚本。

而且整个堆栈还不够


我们在前端到处都有JavaScript。 在服务器,客户端和内部工具的支持下。 我们知道其他语言,但是javascript做得很好。

但是BEM在我们的任务框架中无法解决所有问题。

什么是BEM?
BEM是Yandex在静态HTML页面和CSS级联生命周期内发明的一种Web开发方法。 尚无组件方法,有必要保持许多服务的一致性。 Yandex并没有吃惊,而是开发了自己的组件方法,该方法如今允许您创建隔离的组件并编写灵活的声明性代码。

BEM不仅是一种方法,而且还是大量的技术和库。 其中一些是针对BEM的特定内容而量身定制的,而某些则可能与BEM体系结构隔离使用。 如果您需要功能强大的模板引擎或项目中DOM上有价值的组件抽象示例,则知道在哪里可以找到它们;)

因此,我们开始将服务转移到React。 其中一些已经存在于基于不同堆栈构建的两个应用程序中:

-Yandex BEM专用的平台;
-React的年轻时尚生态系统。

Yandex技术


是时候告诉你为什么我爱上了BEM。

重新定义级别


级别,级别,级别... BEM! 赢利!
覆盖级别是BEM方法的主要特征之一。 要了解它们如何工作,请查看图片:


图像由覆盖层形成。 每层更改最终图片,但不更改其他层。 该图层可以轻松拉出或添加,图片将发生变化。
覆盖级别对代码的作用相同:


组件的行为是在代码汇编过程中形成的。 要添加其他行为,只需将所需的关卡连接到装配体即可。 来自不同级别的模块代码,好像彼此层叠在一起。 在这种情况下,源代码不会更改,但是结合不同的级别,我们会得到不同的行为。

什么级别
上图显示了几个重新定义级别:
  • 基本层-库-提供源代码模块;
  • 下一级别-项目-修改此模块以满足项目的需求;
  • 更高的平台-使相同的模块专用于不同的设备;
  • 蛋糕上的樱桃-实验水平-更改了用于A / B测试的模块。


项目级别与库级别无关,因此库易于更新。 平台级别允许您将不同的程序集用于不同的设备。 并且连接了实验级别,以对用户进行测试,并且在获得结果时也很容易将其关闭。

开发人员自己决定需要什么级别:您可以在不同框架上创建带有主题的级别或具有相同代码的级别。

级别使您可以基于简单模块编写复杂模块,轻松组合行为并在服务之间混淆相同的代码。 这些代码是由BEM世界中的ENB -Webpack收集的。

当我熟悉BEM时,我对内置有现成组件的UI库特别满意。 我们正在新库的框架内扩展这些组件,并在项目之间共享它们。 这使工作变得更轻松:我很少化妆,不编写相同类型的JS,而是从现成的块中快速组装接口。



现在,我们将仔细研究BEM平台的工具,以了解BEM的不足之处以及为什么它不适合我们的任务。

BEM-XJST


我将从我最喜欢的bem-xjst模板引擎开始。 在Yandex.Money之前,我使用了Jade,而Bem-xjst完美地说明了Jade的缺点,当时我没有看到。 bem-xjst模板是声明性的[1],没有地狱的情况[2],并且完全满足组件方法的要求[3]。 在示例中可以清楚地看到所有这些:



沙盒中,您可以查看模板的结果并进行处理。

如何运作? 内部是完美建筑的秘诀;)
  • 写BEMJSON。 BEMJSON是描述BEM树的JSON。 BEM树是DOM树作为独立组件的表示;
  • bem-xjst接受BEMJSON作为输入并应用模式。 可以将此过程与浏览器中的呈现进行比较。 浏览器绕过DOM树,并逐渐将CSS规则应用于其DOM节点:大小,文本颜色,缩进。 Bem-xjst还绕过BEMJSON,搜索与其节点相对应的模板,然后逐步应用它们:标记,属性,内容。 “应用模板”是指从模板生成HTML字符串。 BEMJSON生成的HTML由模板引擎之一-BEMHTML处理。


编写模板很简单:选择实体并编写模板引擎将调用以渲染HTML字符串部分的函数。 最困难的是突出本质。 正确的实体是良好架构的关键!

胡子越长,您已经注意到模板名称中的引用的可能性就越高: XSLT (可扩展样式表语言转换)=> XJST(可扩展JavaScript转换)。 它使用了XSLT的原理,因此具有声明性。 如果您不知道XSLT是什么,请认为自己很幸运:)

Bem-xjst是同构的。 我们在服务器上呈现HTML页面,并在客户端上动态更改它。 为了在运行时进行模板化,bem-xjst提供了我们在编写客户端javascript代码时使用的API。

我要


我们使用bem-xjst描述视图,并使用i-bem描述逻辑。 I-bem是DOM的抽象,它提供了用于处理组件的高级API。 简而言之,让我们这样写:



相反:



要编写代码,您不需要了解组件的内部实现。 我们使用模板中描述的实体进行操作:无论如何,它将是jQuery选择器或DOM元素。 我们可以创建适合特定对象模型的自定义事件,并使用本机事件和接口将其隐藏在内部实现中。 此处还描述了低级逻辑,这意味着我们不会通过不必要的检查将代码加载到主逻辑中。 结果,该代码易于阅读并且不依赖于特定技术。

I-bem允许您将组件的逻辑描述为一组状态[1]。 这是声明性的javascript。 I-bem实现了自己的事件发射器:当状态改变时,组件会自动生成另一个组件可以订阅的事件[2]。

这是大多数BEM客户端javascript代码的样子:



如何运作
  • 通过domReady i-bem事件,它在DOM树中找到组件(块)并对其进行初始化-在浏览器内存中创建与该块相对应的js对象;
  • 在必要事件发生时,我们设置反映状态的块标记。 标记的作用由CSS类执行。 例如,当我们单击输入时,我们在其上添加了“ input_focused”类,该类用作标记;
  • 设置此类标记时,i-bem将启动该块的javascript实现中指定的回调。

编写逻辑很简单:您需要描述块的可能状态(相同的标记)并设置用于更改这些状态​​的处理程序(相同的回调)。

使用i-bem,我们可以轻松地重新定义组件的行为,为它们之间的交互创建格式正确的API,并在运行时动态更改它们。 那还缺少什么呢?
我们喜欢BEM的声明性,易扩展性和高级抽象性,但现在还不准备忍受其限制。 下面,我们将考虑客户端呈现,数据存储以及BEM平台的其他限制的问题。 随着时间的流逝,BEM贡献者可能会解决这些问题,但是我们不准备等待。

具有SPA和对移动设备的适应性的现代网络也需要我们适应性。 因此,我们决定切换到我们自己的堆栈。 他们选择了React。

新的React Maple堆栈


React将虚拟DOM,热重载,JS中的CSS以及我们已经参与的大型社区带入了我们的生活。

我们的服务向React的迁移正全面展开,一些应用程序已被全部或部分重写为React。 我们了解新的方法和工具,并改善应用程序的体系结构。

图书馆


使用React组件方法将接口实体划分为独立的BEM块非常友好。 Yandex开发人员编写了bem-react-core ,并将基本组件UI库转移到React。 我们在上面编写了一个适配器库,其中考虑了这些组件的细节并将它们作为HOC提供



这些库不是在应用程序中连接的,而是在组件的主库中连接的:



该应用程序仅依赖于主库,并从中获取所有组件:



这减少了应用程序依赖性的数量,并且在不同版本下,库不会两次进入捆绑软件。

技术领域


React与特定技术无关,我们自己选择工具和库。 我的装备中有axios,redux,redux表单,redux thunk,样式化组件,TypeScript,flow,jest和其他很棒的东西。 为了防止出现这种动物园,我们与其他开发人员协调使用新技术-我们将拉取请求发送到特殊的存储库,并分析该技术的实用性和选择原因。

前端进入酒吧,酒保告诉他


对于React上的应用程序,我们正在创建一个平台,该平台将汇集库和流程以创建和支持它们。 该平台的核心是Frontend Bar控制台实用程序。 酒吧可以煮很多美味的食物。

在菜单中:

  1. 用冰进行配置:bar混合并摇晃您的yml变量,并为ansible准备了一个配置模板。
  2. 带有配置器香气的果汁:bar将基于模块化空白-果汁创建一个新的应用程序。
  3. 一组基本库设置。 即将推出。

创建多汁的应用程序现在很容易-前端栏制作果汁。 做果汁,而不是战争! Bar部署新应用程序时,它将从Juice执行一组配置:package.json,.babelrc,关键中间件和路由代码,根组件代码。 Frontend Bar将促进新微服务的分配,并有助于遵守编写代码的统一规则。

转移到新堆栈时,我们开始改进应用程序的服务器体系结构-我们为客户端编写了一个新的记录器,并为该库编写了带有一组用于实现MVC的抽象的库。 今天,我们决定采用哪种新服务器架构。



剧透:选择洋葱。

发生了什么,情况变好了吗? 让我们来了解


动态界面



我在上面写过,bem-xjst提供了一个用于在运行时进行模板化的API。 反过来,I-bem可以使用DOM树。 我们将使他们成为朋友,并且我们将能够动态生成和修改HTML。 让我们尝试按事件更改按钮:




在此示例中,BEM的弱点可见:i-bem不想与bem-xjst成为朋友,也不想了解有关模板的任何信息。 它将一个类添加到该块,但不应用模板[1]。 我们手动重新渲染组件[2]:

  • 描述新的BEM树[3];
  • 然后应用新模板[4];
  • 并在当前DOM节点[5]上初始化另一个组件。

此外,i-bem不会创建BEM树的差异,因此将呈现整个组件,而不是已更改的部分。 考虑一个简单的示例:根据需要重新渲染模式窗口的内容。 它包含三个元素:



为简单起见,我们假设只有一个元素可以更改。



我想做[1]并放松。 但是i-bem将不了解发生了什么变化,无法完全重新渲染整个组件并放松。 在此示例中,不会有严重的后果,但是如果整个表单的呈现方式不正确,该怎么办? 这会降低性能,并导致令人不快的副作用:输入闪烁的某个地方,无主工具提示会挂在某个地方。 因此,我们很伤心并手动控制组件的一部分以制作点渲染器[2]。 这使发展复杂化,我们再次感到难过。

已成为


反应来了,毁了一切。 他本人监视组件的状态,我们不再管理手动呈现,也不考虑与DOM交互。 React包含一个虚拟DOM实现。 调用React.createElement会创建DOM节点的js对象及其属性和后代-该组件的虚拟DOM,存储在React内。 当组件发生更改时,React计算新的虚拟DOM,然后计算已保存的和新的diff的差异,并仅更新DOM中已更改的部分。 一切顺利,我们只能使用shouldComponentUpdate优化复杂的逻辑。 成功了!

资料储存



在BEM中,我们准备服务器上的所有数据并将其传输到页面组件:



这些组件是隔离的,彼此之间不会共享数据,这意味着必须将相同的数据放入不同的组件中[1]。 我们将无法在客户端上获取它们,因此每个组件都预先接受其操作的所有可能情况所需的一组数据。 这意味着我们向组件加载了可能不需要的数据[2]。

有时,一个全局实体可以拯救我们,其中一部分公共数据存储在其中,但是变量的全局存储与BEM概念不太吻合。 因此,我们编写了一个bem-redux ,使Redux适用于BEM。 Redux是一个状态管理器,用于管理数据流。 它可以在简单的界面中完美地管理我们的数据,但是在开发复杂的组件时,我们遇到了我上面描述的渲染问题。 Redux对i-bem不友好,我们修复了一些错误并且感到悲伤。

已成为


Redux + React = <3
Redux将整个应用程序的数据存储在一个地方[1]:



组件本身决定何时需要什么数据[2]:



我们只需要描述组件[3]的场景,并指出从何处获取数据以执行它[4]:



React将完成其余的工作[5]:



这种方法使您可以遵循单一职责的原则,并将组件的逻辑封装在组件本身中,而不是在页面代码中进行扩展。 成功了!

你必须付出一切


为了获得成功,我们在React上付出了大量的遗产。 看到几个月前编写的代码如何顺利地弃用,真是令人痛苦。

事实是React是一个视图层库,而不是一个成熟的框架。 您可以选择所有工具,但是必须选择所有工具。还要自己组织代码,制定解决典型问题的方法,制定一套协议并编写缺少的插件。我们为redux表单编写了自己的验证器,但还没有学会如何处理复杂的动画。而且我们尝试扔掉,写和重写。而且我们并不总是重写它,这就是为什么我们的积压量不断增加的原因。

与BEM不同,React还很年轻,还没有为企业发展做好准备。当我们学习烹饪时,我们弄乱了整个厨房,弄乱了肘部。而且,我们仍在辩论是否需要流程,并且仍不完全了解在商店中存储什么以及在本地商店中存储什么。我们根据需要写信,并去参加会议以了解如何做。我们击败了锥体,但自信地前进了。

意想不到的面包


新的堆栈使我们可以重新审视许多任务,并提供了解决这些问题的简单方法。

JS中的CSS



考虑一下生活中的一个简单案例:通过事件对图标进行着色和动画处理,如下所示:



一切代码都不是:



正确,根据BEM的规则,您必须将其



分类为三个目录:Overshot?有争议的点。更重要的是,在js中,当必要的事件发生时,我们会手动添加这些类。通常的情况是,接口的自定义或复杂程度越高,您添加和删除类的频率就越高。如果您不仅需要更改图标,还需要更改文本?不完全是您想要在js代码中看到的逻辑:



但是,如果动画的持续时间取决于某些因素并且是动态设置的,该怎么办?然后,我们将用jQuery重写CSS动画,并为此感到有点难过。

已成为


样式组件,我爱你!JS中的CSS-一种爱!我内心的排字工人很高兴:



模块化得以保留,CSS动画有效,而类无需人工操作。新堆栈的不错奖励。

打字



我们曾经写过很多jsDoc。让我们看看它是否有用:



此示例摘自生产代码。状态包含什么?我不知道 是的,有一个自述文件,但可惜,它有点过时了。是的,我们感到ham愧,但是由于文档和评论经常发生,因此不可靠。将不得不深入研究代码。或不要深入并意外破坏一切。我们很着急,不要深入,休息并感到难过。

已成为


打字来营救。“ Tyk”的类型,以及方法的所有来龙去脉。懒得理解?预提交检查器将开始流程,您仍然需要弄清楚。

我不喜欢一见钟情。日期已经到了,经理发出了砰的一声,您已经“无法获得财产”,这里是“财产缺失”。但是最近我被告知类型可以由O_o设计,如何按类型设计?像这样:



我的世界颠倒了。人流不再是一场噩梦。在编写代码之前用类型描述API模块非常方便且有用。可靠的代码-不错的奖励!

因此,不再需要BEM吗?


不行BEM仍然存在,我们将继续支持BEM堆栈上的应用程序。随着时间的流逝,他们也将迁移到React,但是现在我们正在为此做准备:我们翻译组件库,形成一组工具和协议,并学习如何计划迁移日期。

在BEM,我们的电子邮件通讯模板引擎已实现。我们在服务器上准备信件,并且上述BEM平台的局限性不会影响此应用程序。使用BEM进行开发是一种合适的优雅解决方案。

此外,我们的设计师使用BEM制作原型,有时还会带给我们预组装的组件而不是布局。即使我们停止在BEM上书写,他仍然会找到我们:)

我阅读了第一部分。那编码员呢?


我参与了一个应用程序从BEM到React的翻译,并发现了一件重要的事情。

在加入Yandex.Money之前,我是一个简单的排字工人,花了一年多的时间,花费了数千吨的HTML和JSX。我没有认真对待前端社区及其不断变化的世界。我不明白为什么要研究第一个Angular以便​​明天忘记它而学习第二个。我不明白为什么将jQuery.Ajax更改为Fetch,然后用Axios替换Fetch。

原来,当您将项目从一个框架转移到另一个框架时,您不仅在移植代码。我们必须分析和改进应用程序的体系结构,理顺逻辑,进行重构。不断地更换工具并不是要赶上炒作浪潮,而是要不断寻求能够满足当时需求的最佳解决方案。动态发展的领域分别为您的产品开发和专业发展做出了贡献。而前端就是这样一个领域。让我们一起为之奋斗吧!

反应大家!

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


All Articles