不该用框架等同于重复造轮子吗?
本文经原文作者授权、由 InfoQ 翻译并分享,转载请注明作者、译者信息和出处。
过去盛行的是 Angular,然后是 React,现在是 Vue.js……其他的像 Ember、Backbone 或 Knockout 什么的险些都快消逝了。一些标准,例如 Web Components,则很少被利用。彷佛每年都会发布一些新框架,比如 Svelte、Aurelia,而且每个框架在做事器端都有对应的工具(开头那些框架对应的 NestJS、NextJS 或 Nuxt,Svelte 对应的 Sapper,等等)。非 JavaScript Web 框架(如 Django、Spring、Laravel、Rails 等)就更不用说了。乃至还有框架之上的框架(Quasar、SolidJS)、为框架天生组件代码的框架(Stencil、Mitosis),以及 NCDP(无代码开拓平台,No-Code Development Platform)。

这种多样性让想知道哪种技能值得学习的开拓职员和技能选型决策者感到困惑。
网络上常常会涌现一些比较这些框架的文章,彷佛是在帮助我们解开这种困惑。但大多数作者常日是带有偏见的,由于他们可能“用过这个框架”,但“只考试测验了一些其他的框架”。偏见程度较低的作者总是得出“这取决于详细情形”的结论(取决于性能、工具、支持、社区等),这实际上是一种非结论性的结论。
纵然一些基准测试基于同一个运用程序对不同的框架进行了比较,也很难得到真实的结果,由于这种基准测试受限于被测试的运用程序(比如待办事项运用程序)。
框架看起来就像是宗教(或者说是政治):每一个框架都假装自己为开拓者供应理解决方案,但每一个又都不一样。它们每一个都声称可以为运用程序供应最好的前景,但关于哪一个真正名副实在的辩论又不绝于耳。每一个框架都哀求你遵照特定的规则,它们之间可能有相似之处,但要从一个框架转换到另一个框架总是很难。
现在,让我们来看看框架的“无神论”方法:不该用框架。
从哪里讲起?
我有超过 25 年的专业软件开拓履历,除此之外,本文还将以构建真实纯 JS Web 运用程序(前端和后端)的履历为根本。
为什么不该用框架?
实际上,这个想法还很新。早在 2017 年,Django Web 框架联合创始人 Adrian Holovaty 就谈到了他的框架“疲倦”,以及他为什么离开 Django 去构建自己的纯 JS 项目。
有人可能会问,为什么会有人想要在不该用框架的情形下开拓 Web 运用程序?为什么不在其他人花了数年韶光和精力的成果的根本上做开拓?或者是由于 NIH(Not Invented Here)综合症导致大家都想构建定制的框架?
开拓职员并不会比一样平常人更方向于自找麻烦,实际上,他们可能比任何人都
但他们又想要敏捷,也便是能够轻松、快速地办理问题。
虽然“快速”彷佛是框架承诺的东西(为你搭建脚手架,并增加可靠性),但它不是免费的:它们想让你签署条约,赞许支付“税”费,并将你的代码放入“孤井”(“税和孤井”的说法来自 IBM Carbon 系统设计团队卖力人 Akira Sud<https://github.com/carbon-design-system/carbon-web-components#readme>)。
框架税
利用框架是须要付出本钱的:
遵照它们的 API 规则,这样它们就可以向你供应做事。这便是框架的事情办法:你的代码必须遵守某些规则,包括或多或少的样板代码。你每天要想的不是“如何做这件事”,而是“如何让框架做(或不做)这件事”。如果你规避了这些约束,风险就由你自己承担:如果你通过直接调用底层 API 来绕过框架,就不要指望它们能理解你的意图,也不要指望它们的行为能保持同等。以是,框架会让你“专注于业务”是一个虚假的承诺:实际上,框架的事情你也没少操心。如果你想要以下这些东西,就不得不逼迫进行升级:
1)想要一个新功能(纵然你不须要所有功能,也必须升级所有东西);
2)想要修复一个 bug;
3)不想失落去框架的支持(随着新版本的发布,你的运用程序所依赖的版本将会被弃用)。
如果框架涌现了 bug,但没有明确的操持修复日期,这会让你感到非常沮丧(可能还会让项目面临风险)。第三方供应的框架库(如小部件)或插件也不例外,如果你一贯利用旧版本,它们与你的运用程序的兼容性会越来越差。对付框架掩护者来说,掩护向后兼容性已经成为一件非常麻烦的事情。他们创造,开拓自动升级代码的工具(Angular 的 ng-update、React 的原生升级助手、Facebook 的 jscodesshift 等)会更有利可图。
须要学习如何利用它们(它们能做或不能做什么、它们的观点、API、生态系统、工具),包括理解在新版本中可能发生的变革。如果你选择的是当前最盛行的框架,这可能会随意马虎些,但你不可能理解一个框架的方方面面。此外,炒作也从来不会消停:如果你决定在一个新运用程序中利用另一个框架(或者更糟的是,从一个框架迁移到另一个框架),那么你在旧框架上所有的投入都将归零。这便是为什么很多企业项目会缺少活力,纵然每个项目都可能与前一个项目不一样。已故的 David Wheeler 曾经说过:“保持兼容性意味着故意重复别人的缺点”。将掌握权委托给框架,这是对框架毛病的妥协:你可能无法做任何你想做的事(或防止框架做你不肯望它们做的事情)或者你大概不能得到你想要的性能(由于额外的分层、普适性、更大的代码体积或向后兼容性需求)。技能零散化。很多开拓职员要么不太理解底层 API(由于他们总是利用框架供应的东西),要么活在过去(只知道过期的知识,不知道最新的改进和功能)。“工具法则”常常导致过度设计,为大略的问题构建繁芜的办理方案,而构建大略办理方案的知识逐渐零散化。在指南的辅导下,我们失落去了(或者没有得到)好的软件设计(原则、模式)文化,并失落去(或者没有得到)构建主要工程的履历。就像 CSS 框架(Bootstrap、Tailwind 等)的用户缺少 CSS 技能一样,Web 框架的用户也注定缺少当代 Web API 和软件设计履历。一旦你把钱放入框架,就很难把它拿出来。
框架孤井
除了必须支付“税”费来得到框架的好处之外,如果框架没有标准化,它们还会带来另一个问题。
由于它们逼迫哀求你遵照框架规则——而且每一条规则都不一样——这意味着你的运用程序将与一个专有的生态系统绑定在一起,也便是用专有 API(及其升级过程)锁定你的运用程序代码。这对付你的项目来说是一个冒险的赌注,正如它们所暗示的那样:
没有可移植性:将代码迁移到另一个框架(或者一个有重大变革的新版本,乃至是不该用框架)将是非常昂贵的,包括可能须要进行重新培训的本钱;你的代码与其他框架运行时或你想要利用的其他框架组件库没有互操作性:由于规则不同,大多数框架彼此之间很难实现互操作。
当然,在项目刚开始时,你可以选择最盛行的框架。对付一个短期的项目来说,这可能是可以接管的,但对付长期项目来说则不然。
框架交往来交往去。从 2018 年开始,每年都有 1 到 3 个新框架取代旧框架。
不过,标准框架并不存在孤井。在 Web 平台(即浏览器框架)上,利用标准 Web API 可以降落你的投入风险,由于它们可以在大多数浏览器上运行。纵然不是所有的浏览器都支持,仍旧可以通过 polyfill 来填补。
例如,现在的 Web 组件既可移植(险些可以在所有浏览器中利用),又可互操作(可以被任何代码利用,包括专有框架),由于它们可以被封装成任意的 HTML 元素。不仅具备更好的性能,它们的运行时(自定义元素、阴影 DOM、HTML 模板)还作为浏览器的一部分运行,以是它们已经在那里(不须要***),并且是原生的。
很少会有开拓者试图逃离框架孤井。
那么框架实质上便是不好的吗?
如果是为实现运用程序逻辑而创建自己的框架,那就不能说框架是不好的:任何运用程序都须要实现自己的业务规则。
如果符合以下这些情形,框架便是好的:
是运用程序特有的:任何运用程序终极都会设计自己的“业务”框架。成为标准,例如,Web 平台便是一个标准的 Web 框架,而 Web 组件框架(lit、stencil、skatejs 等)终极构建的组件都符合这个标准。添加一些其他办理方案(包括其他框架)所短缺的独特代价。对付这种情形,你险些没有选择,这些附加代价证明了隐含的锁定本钱是合理的。例如,一个特定于操作系统的框架遵照了操作系统的标准,除此之外没有其他办法可以得到能够知足需求的运用程序或扩展。用于构建非关键(短期、低质量预期,并且可以接管“税费”和“孤井”)运用程序。例如,利用 Bootstrap 构建原型、MVP 或内部工具。
去框架化的目标大略地说,避免利用框架来构建运用程序的目标是:
通过避免框架的“一刀切”约束来最大化灵巧性。此外,去掉规则的约束,提升运用程序的创造力。大多数利用 Bootstrap 开拓的 Web 运用程序都属于此类,由于它们很难摆脱预定义组件和样式,终极将很难从其他角度思考问题。只管即便减少对炒作过度的框架的依赖。不被框架锁定,才能够避免可移植性和互操作性方面的问题。只在须要时进行最细粒度的操作(例如,不依赖框架的刷新周期),并减少依赖项,只利用一些必需的轻量级库,以此来最大化性能。
当然,我们的目标也不能是“重新发明轮子”。我们来看看该怎么做。
框架之外的选择
那么,如何在没有框架的情形下开拓运用程序呢?
首先,我们必须明确一个反目标:不要将“不该用框架构建运用程序”与“取代框架”稠浊起来了。框架是一种用于托管任意运用程序的通用技能办理方案,以是它们的目标并非你的运用程序,而是所有的运用程序。相反,分开框架才有可能让你更专注于你的运用程序。
不该用框架开拓运用程序并不虞味着要重新实现框架。
要评估在不该用框架的情形下构建运用程序的难度,我们要明白:它不像构建框架那么困难,由于以下这些不是我们的目标:
构建专有的组件模型(实现特定组件生命周期的容器);构建专有的插件或扩展系统;构建一个奇特的模板语法(JSX、Angular HTML 等);实现通用的优化(变更检测、虚拟 DOM);特定于框架的工具(调试扩展、UI 构建器、版本迁移工具)。
因此,构建一个普通的运用程序并不是一项艰巨的“重新发明轮子”的任务,由于这个“轮子”紧张是关于 API/合约、实现、通用引擎和干系的优化、调试能力等。放弃通用目标,专注于运用程序的目标,这意味着你可以摆脱大部分目标,而这才是真正的“专注于你的运用程序”。
那么,我们该如何设计和实现一个普通的运用程序?由于大多数运用程序都是利用框架构建的,以是如果没有这些熟习的工具,确实很难设计出一种方法来实现类似的结果。你必须:
改变你的想法:不要利用特定于框架的做事。对付一个普通的运用程序来说,你可能不须要这些做事。不须要变更检测,直接更新 DOM 即可……用其他技能替代方案来实行原来利用框架实行的常见任务(更新 DOM、延迟加载等)。
一些作者,如 Jeremy Likness 或 Chris Ferdinandi(被称为“JS 极客”)也提到过这个话题。但是,根据定义,任何一个普通的运用程序都可以选择(或不选择)利用个中的一种技能,详细视需求而定。例如,MeetSpace 的作者只须要利用标准 API 就足以。
接下来,让我们来看看一些常见的“解法”。
标准
标准 API 属于“好的框架”,由于它们:
具备可移植性:它们在任何地方都可用,如果不可用,可以通过 polyfill 的办法实现。具备互操作性:它们可以与其他标准交互,并被用在专有代码中。长期存在:由多个行业参与者设计,而不但是一个。它们被设计得很好,一旦发布就会一贯存在,利用它们的风险较小。在大多数情形下在浏览器中都是立即可用的,避免了***过程。在某些情形下,你可能须要*** polyfill。但是,与专有框架(注定会越来越不盛行)不一样的是,它们的可用性会越来越高(逐渐降落***的必要性)。
在选择编程措辞时,我们要着重考虑标准。JavaScript 经由多年的发展,现在也包含了在其他编程措辞中涌现的特性,比如 class 关键字和通过 JSDoc 注释(如 @type)供应有限的类型检讨支持。
很多编程措辞可以被编译成 JavaScript:TypeScript、CoffeeScript、Elm、Kotlin、Scala.js、Haxe、Dart、Rust、Flow 等。它们都为你的代码添加了不同的代价(类型检讨、额外的抽象、语法糖)。普通的运用涌现该当利用它们吗?为了回答这个问题,让我们来看看它们是否隐含了与框架相同的缺陷:
遵照语法:大多数编程措辞都逼迫哀求这么做(CoffeeScript、Elm、Kotlin 等)。但须要把稳的是,它们是 JavaScript 的超集(TypeScript、Flow),你仍旧可以用纯 JavaScript 编写你选择的某些部分。如果你利用的是非常旧的编程措辞(包括 JavaScript)版本,就须要升级,但升级频率比框架低很多。须要学习它们的语法。不过,你可以循规蹈矩地学习超集编程措辞,由于你的代码的某些部分可以连续利用传统 JS。对付非超集编程措辞来说,离散化技能确实是一个风险。由于它们的编译具有普适性,可能不是最优的,而你可能没故意识到这一点。大概你可以利用更大略和高效的 JS 代码来完成同样的操作。须要对缺陷做出妥协,由于我们无法改变转译成 JS(或者利用 tsconfig.json 做一点定制)或编译成 WebAssembly 的过程。有些措辞可能还会忽略 JS 的一些观点。具备可移植性,由于常日代码可以转译到 ES5(但有时你不得欠妥协,纵然你想要转译到 ES6)。WebAssembly 很新,所有当代浏览器都支持它。供应与其他 JS 代码的互操作性。例如,Typescript 可以被配置为支持 JS。
在一个普通的运用程序中,我们要小心谨慎地利用非超集措辞,由于它们或多或少都隐含了一些约束。超集措辞(TypeScript、Flow)通过避免“要么全有要么全无”来最小化这些约束,我们该当在它们可以带来代价的地方利用它们。
须要把稳的是,在 JavaScript 之上构建的措辞层意味着我们的工具链中又增加了一层繁芜性,可能会由于某些缘故原由招致失落败(见下文)。此外,在经由编译或转译之后,开拓阶段的好处也会消逝(常日在运行时不会逼迫实行类型或可见性约束检讨)。
开拓库
基于不“重写框架”的假设,就会得出普通的 JS 运用程序不应该利用开拓库的结论。这是完备缺点的。“重新发明轮子”,即从头开始重写统统,并不是一个明智的目标。我们的目标是肃清框架(而不是开拓库)中隐含的约束,请不要将其与“自己编写统统”的教条稠浊在一起。
因此,如果你自己不能编写某些代码(可能是由于没有韶光,或者由于须要太多的专业知识),利用开拓库并没有什么错。你只须要关心:
模块化:如果你只须要一小部分功能,就要避免依赖全体大开拓库;避免冗余:在没有标准的情形下才利用开拓库,并优先选择实现了标准的开拓库;避免锁定:不要直策应用开拓库的 API,而是把它们包装在运用程序 API 中。
须要把稳的是,不要被那些声称它们不是框架的文档或文章所迷惑(由于它们“没有被明确定义”成框架,或者没有定义一个“完全的运用程序”):只要隐含了约束,它们便是框架。
模式
Holovaty 说,只是运用模式(不该用框架)来构建软件是不足的。
模式是众所周知的东西,不特定于某种开拓过程。它们本身是自我文档化的,由于它们可以被有履历的开拓职员快速识别出来。
这里仅举几个例子:
模型、视图和掌握器模式(MVC);根据配置创建工具的工厂模式;简化反应式编程的不雅观察者模式;用于遍历凑集的迭代器模式;用于延迟加载、安全检讨的代理模式;用于封装操作(可能基于高下文被触发)的命令模式。
这样的模式有很多:你可以自由地用它们来知足你的需求。如果一个模式为你的运用程序的一个范例问题供应了范例的办理方案,你一定要用它。更宽泛地说,任何符合 SOLID 原则和具有良好内聚力的东西都有利于运用程序的灵巧性和可掩护性。
更新视图
在口试开拓者时,当被问及在构建一个普通运用程序时他们紧张会担心哪些东西时,他们大多数会回答:实现繁芜的模型变革检测和后续的“视图”更新。这是范例的“工具法则”效应,它会让你按照框架的思路思考问题,但实际上你的一些大略的需求根本不须要用到框架:
“视图”只是 DOM 元素。你当然可以对它们进行抽象(你也该当这样做),但终极它们也只是抽象而已。更新它们只是调用 viewElement.replaceChild(newContent)的问题,不须要更新更大范围的 DOM,也不须要重画或滚动。更新 DOM 的方法有好多种,可以插入文本,也可以操作实际的 DOM 工具,只要选一个适宜你的就行了。在普通运用程序中,“检测”什么时候须要更新视图常日是没有必要的。由于在大多数情形下,你只知道在一个事宜之后须要更新什么,然后你直接实行这个命令就可以了。当然,在某些情形下,你可能须要通过反转依赖和关照不雅观察者(见下文)来进行一样平常性的更新。
模板开拓职员不肯望缺失落的另一个特性是编写带有动态部分或监听器的 HTML 片段。
首先,DOM API(如 document.createElement("button"))并不是那么难,而且实际上比任何模板措辞都更强大,由于你可以全面访问这些 API。编写很长的 HTML 片段可能很乏味,如果它们真的很长,可以将它们拆分成更细粒度的组件。
不过,将这些元素视为模板确实可以提高可读性。那么该如何管理它们呢?这里有多种方法:
现在可以在浏览器中利用 HTML 模板了(实际上从 2017 年就可以了)。它们供应了构建可重用的 HTML <template>片段的能力。这实际上是 Web 组件的一部分。JavaScript 从 ES6(2015)开始支持模板字面量,你可以很轻松地将值嵌入到字符串中。你可以嵌入原始类型(数字、字符串,包括其他 HTML 代码等),但不能嵌入更繁芜的元素,例如注册了监听器的 DOM 元素。我们可以借助标记模板字面量函数将繁芜的值(如 DOM 节点)嵌入到模板中。ObservableHQ 已经设计了一个非常方便的工具,可以用它编写 html`<header>${stringOrNode}</header>这样的代码,或者实现更繁芜的模板,比如 html`<ul>${items.map(item => `<li>${item.title}</li>}</ul>。
模板中的条件或循环语句该怎么办?且不说这可能从来都不是一个好主张(UI 中不应该包含逻辑),你可以(也该当)只用 JS 来实现逻辑,然后利用上面的技能将结果插入到模板中。
事宜
现在,我们有了基本的模板,那么该如何将事宜绑定到 DOM 节点呢?这里也有几种选择:
HTML 事宜处理器代码(<button onclick="myClickHandler(event)">)可以被插入到 HTML 源代码中,但这并非最好的办法,由于指定的处理器只在指定的范围内可用。事宜处理器 API(button.addEventListener("click", myClickHandler))可用于所有通过 DOM API 或 HTML 标记模板字面量函数创建的节点。
那么定制或业务事宜该怎么办?如果我须要对运用程序的某个组件触发的一些事宜作出反应该怎么办?这里也有多种处理办法:
自定义事宜:你可以通过扩展 EventTarget 来创建自己的事宜类,并派发或监听它们,就像“标准”事宜一样。理论上说,利用 EventEmitter 也是一种办法(存在于 Node 中,在浏览器中作为库存在),但它很少被利用。不雅观察者模式:你可以构建自己的不雅观察者,也可以考虑利用 RxJs,它是这方面的标准。你只须要构建一个 Subject,并在发生事宜时关照所有订阅者,让订阅者对事宜做出反应。
组件虽说开拓普通的运用程序不同于开拓繁芜的根本举动步伐(也便是用于托管组件的容器),但如果一些东西在系统中会多次涌现,那么将它们设计成可重用组件(与高下文无关)仍旧是一个好主张。无论你利用何种技能,也无论是业务还是技能,一定程度粒度的抽象仍旧是有用的:将与同一业务观点干系的数据和规则封装成一个可重用的工具,或者构建可以在运用程序多个地方进行实例化的小部件,总归是个好主张。
创建组件的方法有很多,详细视自己的需求而定。早在 2017 年,Mev-Rael 就提出了很多技巧,用于处理 JavaScript 组件的状态、自定义属性和视图。当然,我们不要拘囿于别人推举的技能,而是要先考虑自己的需求,然后再选择得当的技能。
除了标准的小部件组件(常日是标准的 Web 组件),任何一个组件都该当能够:
将逻辑和视图拆分开(常日会利用 MVC 模式)。把它们稠浊在一起常日会导致代码不易于掩护,还会降落灵巧性(例如,如果你想同时以详情或表格的形式显示一条记录,你的 RecordComponent 只须要利用 DetailRecordView 或 RowRecordView)。参数化组件的行为或视图。通过触发事宜的形式关照订阅者组件中发生了某些事宜(常日是在发生用户交互之后)。同步:如果发生一些事宜,组件该当能够进行重绘。这个利用反应式开拓库(如 RxJS)可以很随意马虎实现。
在任何情形下,无论你选择了什么样的设计谋略,你的组件(或者更详细地说,它的干系“视图”)都必须能够供应一些 HTML 渲染结果。你可以利用包含 HTML 代码的字符串,但 HTMLElement(或 Element)常日是更好的选择(可读性高,直接更新,可以绑定事宜处理器),而且性能更好(不须要解析)。
此外,你可能希望利用来自第三方的外部组件。由于专有框架的盛行程度较高,它们可以更大程度地利用社区开拓的库和组。它们中的大多数实际上与纯 JS 实现的特性(比如 JQuery)并没有太大不同,但问题是,它们缺少互操作性,以是到末了你会创造自己须要的实在是纯 JS 或 Web 组件。
所幸的是,这样的库确实存在,比如 Vanilla JS Toolkit,只管可能不太常见。在 Web 组件方面,webcomponents.org 列出了 2000 多个元素。乃至还有普通的 Web 组件,只是它们与我们要谈论的不太干系(更多的是关注轻量级实现,而不是互操作性)。
路由
在 SPA 中管理路由须要利用 Web History API。虽然这并不繁芜,但你仍旧可能希望将其委托给大略的路由器库,如 Navigo。
你所要做的便是在路由时用一个 DOM 元素更换另一个 DOM 元素(利用 replaceChildren()或 replaceWith()方法)。
延迟加载
按需加载 JavaScript 代码是任何一个 Web 运用程序都须要考虑的问题。你一定不肯望为了显示一个登录界面而加载全部的运用程序代码。
早在 2009 年,在 Web 框架涌现之前,James Burke(Dojo 开拓者)就发布了 RequireJS(最开始叫“RunJS”)来办理这个问题。从那时起,随着模块化的涌现,涌现了更多的技能。从 ES6(2015)开始,我们可以动态加载代码。在 Node 中可以,在浏览器中也可以:
{WelcomeModule} = await import("./welcome/ModuleImpl")module = new WelcomeModule()
复制代码
那么如何将模块分拆到单独的文件中?打包器(如 Webpack)可以为你做这些事情。
须要把稳的是,在导入路径里你该当只利用常量,否则打包器就无法猜到你想要加载什么,就会将所有可能的文件都打包在一个文件中。例如,await import(`./welcome/${moduleName}`) 将把所有东西都打包到指定的目录中,由于打包器不知道变量 moduleName 在运行时会是什么。
原生运用程序
越来越多的框架为原平生台(如 React Native)供应了运行、迁移或编译运用程序的方法,以便将它们作为独立运用程序支配到 Android 或 iOS 移动系统上。
除了考虑开拓真正的原生运用程序之外,更普遍的办理方案是将 Web 运用程序嵌入到原生容器中,比如之前的 PhoneGap(现已停滞掩护)或 Apache Cordova,现在的 NativeScript(它支持框架,如 Angular,也支持普通的运用程序),或者像 Electron 这样的原生 Web 运用程序包装器,或者 Electron 的轻量级后继者 Tauri。
做事器端渲染
很多框架在前端和后端运行的代码是相似的,这样更随意马虎实现对 SEO 友好的做事器端渲染(SSR)。
这可能是一个又酷又便利的特性,但须要把稳的是,它也可能导致做事器锁定。因此,在向运用程序引入框架锁定之前,你须要考虑它对项目、根本举动步伐、客户端技能等方面的影响。
所幸的是,你也可以在不该用框架的情形下实现这个特性。
从做事器端渲染
采取普通的实现方案在一开始看起来很大略:不便是返回 HTML 吗?是的,你已经有现成的组件了,但是:
你还须要一个做事器端 DOM API,由于默认情形下,做事器端不供应 DOM API(由 Domenic Denicola 卖力掩护的 JSDOM 或经由优化的 Happy DOM 便是很好的选择)。你的渲染组件不能假设是 DOM 是在客户端或做事器端,也便是说,不要利用全局 DOM,由于在做事器端,每个要求都须要一个 DOM。要做到这一点,你须要从(客户端或做事器)运用程序高下文中选择 DOM 工具(window document 和类型,如 Node、HTMLElement、NodeFilter),而不是直接获取。在客户端和做事器运用程序之间共享渲染组件有多种办法,比如将其发布在包存储库中,但最灵巧的该当是让运用程序包引用 monorepo 中的模块。
添加交互性然而,一旦 HTML 元素被转换成字符串,在这些元素上设置的所有事宜处理器都丢失了。为了规复交互性,你须要一些“补水”步骤,也便是注入脚本,让它们在客户端实行。框架因其普适性很难做到这一点。就拿影子 DOM 来说,它们不断考试测验改进算法,希望能够以最聪明的办法做到这一点,但如果我们把问题缩小到运用程序层面,就会变得大略很多。
当然,在普通的做事器运用程序中做到这一点也意味着须要将 JS 脚本注入到相应中(通过引用或内联,详细取决于你想要若何的“渐进”程度,比如将 Web 组件所需的代码嵌入到 HTML 相应中,让它们在客户端实行)。
普通的办理方案让你可以掌握在哪里、什么时候以及附加哪些东西:你可以先只发送 HTML,再加载基本的交互性 JavaScript,然后加载更多(取决于用户的操作),等等。
这比本文中提到的任何一个东西都大略,由于它们是运用程序代码,而不是通用的框架代码。
国际化
多年来,国际化问题都是通过库来处理的(终极也被集成到框架中)。要自己集成这些库也很随意马虎,但你也可以选择自己实现一个,由于与通用库比较,自己的实现可以支持更大略、更有效的类型。
便是这么大略:
interface WelcomeMessages { title: string greetings(user: string, unreadCount: number): string}class WelcomeMessage_en implements WelcomeMessage { title = "Welcome !", greetings = (user, unreadCount) => `Welcome ${user}, you have ${unreadCount} unread messages.`}class WelcomeMessage_fr implements WelcomeMessage { title = "Bienvenue !", greetings = (user, unreadCount) => `Bienvenue ${user}, vous avez ${unreadCount} nouveaux messages.`}
复制代码
这里为你供应了:
类型检讨:每个都有一个静态类型(和几个翻译实现),以是 IDE 可以检讨你是否利用了有效的属性,并为你供应自动补全功能。翻译完全性检讨:在为所有键供应所有措辞的翻译之前,无法通过编译。
你所须要做的便是(加载和)实例化与用户措辞环境干系的类。通用库不会供应这种特定于业务的类型。
工具
如果你想要摆脱对强约束性软件技能栈的依赖,那你很可能也想摆脱对工具的依赖:你不肯望只有靠着它们(它们的局限性、性能、缺点、版本)才能向前走。你不肯望被一个你无法办理的构建问题(或者须要数小时或数天才能办理)所困扰(特殊是如果你利用的是最近构建的版本,而它们还没有经由充分的实战测试)。
话虽如此,你仍旧很难避免利用这些工具。大多数情形下,你的产品代码必须以某种办法打成包,包括缩小体积、稠浊、代码拆分、摇树优化、延迟加载、包含样式等。毫无疑问,现有的打包工具如 Webpack、Parcel、ESBuild 或 Vite 会做得比你更好。
你所能做的是:
尽可能少用转译。例如,利用 TypeScript 可能是件好事,但它会带来额外的繁芜性,你的工具链中必须有相应的工具来处理这种繁芜性。CSS 也一样,特殊是最新版本,不值得你用预处理器(如 Sass)来处理它们。尽可能少用工具。你用的工具越多,就越有可能出问题或无法知足你的需求。如果确实须要利用工具,请选择最盛行的工具,由于它们经由实战测试,更有可能知足你的需求(这样你就不会陷入“改变需求或改换工具”的困境)。过早利用最新的打包工具可能会为你节省几秒钟的构建韶光,但这些韶光很可能都不足用来理解工具文档、处理 bug 或处理因缺少支持而导致的问题。
最大的寻衅说到底,最大的寻衅不是技能上的,而是关于人的:
你要走出舒适区。希望你终将能够明白,利用普通的办理方案并不是那么困难,框架的繁芜性比它们带来的好处要大得多。此外,你可能会看到更多新的 API(WebComponents、ES6 模块、代理、MutationObserver……),而且 Web 比你想象的更当代、更强大。至于其他人,你可以考试测验说服他们。他们可能不愿意这么做,由于任何人都不愿意开启自己从未考试测验过的旅程。
其他人可能会跟你说:
“你要开拓自己的框架”:不,我们要开拓的是运用程序,而不是框架。“你要写更多的代码”:大概,但大概不会太多(取决于用了多少开拓库),由于这须要与框架的样板代码进行比较。但不管若何,须要加载的代码都会更少。“你将不断地重新发明轮子”:当然不是。不该用框架是为了不遵照它们预定义的规则(配置、生命周期管理、刷新机制等),但我们并没有忘却 DRY 原则,我们仍旧可以(并且该当)利用经由实战测试的第三方库。“你须要为每一个功能写更多的代码”:不,你可以遵照自己的规则,而不是利用框架样板代码。“没有文档可看”:肯定不会有框架文档(由于根本就没有框架),但你仍旧须要写运用程序文档。值得一提的是,利用模式有助于你自动文档化你的软件设计。你只须要关心运用程序的代码文档,而如果你多利用一个框架,就须要多看一份文档。“不会有约束或模式来辅导开拓职员”:不,如果你确实须要约束,没有什么能阻挡你(你只须要定义左券就行了)。“你会错过性能提升”,比如曾经被大肆炒作的虚拟 Dom(如今受到了寻衅,包括来自 Svelte 或 Aurelia 框架的寻衅):不,由于须要这些“性能提升”的是框架本身(为了通用性),而不是运用程序。相反,通用框架更有可能错过一些可以通过自定义代码实现的性能提升。你碰着这个问题是由于你没有利用框架。每一个问题(包括漏洞、延迟、招募等)都会被归咎于由于没有利用框架。由于大多数开拓职员的履历是,所有正常运行的东西都利用了框架,默认情形下,不该用它们将被认为是有风险的。一旦涌现问题,无论是否与不该用框架有关,这个假设都会被认为是精确的。他们忘却了在利用框架时也会碰着类似的问题。“我们找不到开拓者”:他们会说很难找到能够写纯 JS 代码的开拓者。这句话是对的,也是错的。由于很多开拓者(且不说管理者)会创造自己更习气于利用框架。如果他们从来没有利用过或不理解基本的 Web API,那么他们可能会对从零开始构建一个 Web 运用程序感到害怕。但是,如果你想要开拓高质量的运用程序,就不应该去找这种类型的开拓者。当然,现在找 React 开拓者很随意马虎,但你须要的不但是 React 开拓者,而是精良的开拓者。“你无法得到与框架相同的代码质量”。当然,框架或开拓库常日是由行业里有履历的开拓者编写的。但是,框架的代码紧张与框架特定的活动干系(组件生命周期、通用的刷新机制和优化、工具,等等),与你的运用程序无关。此外,纵然利用了框架,你仍旧可能做出糟糕的设计,写出糟糕的代码。运用程序的质量总是更多地取决于团队的质量,而不是由于短缺框架。“你无法得到与框架相同的性能”:不,我们可以得到更好的性能。行业里关于框架采取了可以“提升性能”的繁芜技能的说法就不在这里谈论了,由于它们可能紧张被用来办理框架通用办理方案的性能毛病(比如虚拟 DOM)。
毫无疑问,性能最好的框架是那些在普通代码之上添加层数较少的框架。框架的“优化”更多的是为了填补框架本身的开销。
结论
不该用框架构建 Web 运用程序并非意味着要自己构建框架,它是关于在不该用通用引擎的情形下开拓运用程序,目的是:
避免散失落掌握和被隐含约束(锁定、升级本钱等);可以进行优化(性能、体积、设计)。
也便是只编写特定于运用程序的代码(业务和技能),包括利用开拓库。你真正该当关注的框架是你自己的框架,也便是那个特定于运用程序的框架。这是真正的“专注于业务”,也是最有效的。
这并没有你想象的那么难,特殊是有了当代标准的加持(在必要时主流浏览器可以通过 polyfill 来支持新特性)。
原文链接:https://javarome.medium.com/design-noframework-bbc00a02d9b3