关于前端:如何逃离框架孤井

39次阅读

共计 13729 个字符,预计需要花费 35 分钟才能阅读完成。

前言

后面我发过一篇文章,脱离了 Spring 询问大家能不能持续开发,后果文章上面的评论和回复都通知我大家伙的根底打得很牢固,该咋写还是咋写。看得我在这内卷的时代霎时躺平。


那么明天挑战降级,不必任何框架开发 Web 应用程序, 你能做到么?

首先,咱们要思考一个问题:

不应用框架等同于反复造轮子吗?

过来风行的是 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)。

这种多样性让想晓得哪种技术值得学习的开发人员和技术选型决策者感到困惑。

网络上常常会呈现一些比拟这些框架的文章,如同是在帮忙咱们解开这种困惑。但大多数作者通常是带有偏见的,因为他们可能“用过这个框架”,但“只尝试了一些其余的框架”。偏见水平较低的作者总是得出“这取决于具体情况”的论断(取决于性能、工具、反对、社区等),这实际上是一种非结论性的论断。

即便一些基准测试基于同一个应用程序对不同的框架进行了比拟,也很难取得实在的后果,因为这种基准测试受限于被测试的应用程序(比方待办事项应用程序)。

框架看起来就像是宗教(或者说是政治):每一个框架都伪装本人为开发者提供了解决方案,但每一个又都不一样。它们每一个都宣称能够为应用程序提供最好的前景,但对于哪一个真正货真价实的争执又不绝于耳。每一个框架都要求你遵循特定的规定,它们之间可能有相似之处,但要从一个框架转换到另一个框架总是很难。所以没有什么一说:Angular 天下第一;Vue 是天。ヾ (=・ω・=)o

当初,让咱们来看看框架的“无神论”办法: 不应用框架

为什么不应用框架?

实际上,这个想法还很新。早在 2017 年,Django Web 框架联结创始人 Adrian Holovaty 就谈到了他的框架“疲劳”,以及他为什么来到 Django 去构建本人的纯 JS 我的项目。

有人可能会问,为什么会有人想要在不应用框架的状况下开发 Web 应用程序?为什么不在其他人花了数年工夫和精力的成绩的根底上做开发?或者是因为 NIH(Not Invented Here)综合症导致人人都想构建定制的框架?

开发人员并不会比个别人更偏向于自找麻烦,实际上,他们可能比任何人都懒:他们只会想写更少的代码(这样他们就能够更少犯错),想要自动化(以防止人为谬误)……

能摆谁不摆呢?

但他们又想要麻利,也就是可能轻松、疾速地解决问题。

尽管“疾速”仿佛是框架承诺的货色(为你搭建脚手架,并减少可靠性),但它不是收费的:它们想让你签订合同,批准领取“税”费,并将你的代码放入“孤井”(“税和孤井”的说法来自 IBM Carbon 零碎设计团队负责人 Akira Sud)。

框架税

应用框架是须要付出老本的:

  • 遵循它们的 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 对象(windowdocument 和类型,如 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 来反对新个性)。

正文完
 0