发布于 2026-01-06 6 阅读
0

无框架Web开发

无框架Web开发

简而言之:现在不用框架进行 Web 开发从未如此简单。如果您不想阅读冗长的介绍,可以直接跳到“无框架开发”部分,了解如何不使用框架来创建可重用组件,或者更准确地说,如何创建新的 HTML 元素。

全新。闪亮。现代。清新。最新。时尚。快速。到货。

作为人类,你总是会被新鲜事物吸引,这一点在你生活的方方面面都能体现出来。你可能口袋里装着一个盒子,现在你用它做所有事情:上网、拍照、记笔记、追踪各种信息(当然,有人也在用它追踪你,但这又是另一个故事了)。它让你保持联系,同时也让你与他人疏远。偶尔,你才会像最初设想的那样用它打电话。等等,现在又出现了一款新的“口袋盒子”(名字取自一部诚实手机的广告),它的名字里有个更高的数字,价格相当于一个人一个月的工资(在某些国家,甚至相当于一个人四五个月的工资)。它不能插耳机,但是屏幕上有个刘海。哇,刘海他们卖给你一些你不需要、也没要求的功能,却能通过编造一些听起来很吸引人的愚蠢名字来让你对他们的产品产生兴趣。比如“无限屏”。你得有多蠢才会相信“全视屏”这种名字,还把它当成新手机的优势和卖点?屏幕的边缘明明就在那儿,根本就不是“无限”的,除非价格上真的有那么大的优势。

你愿意花大价钱,牺牲隐私和时间,只为了不错任何新鲜热门的东西。你花钱买的东西,即使属于你,也无法定制。当然,你可以装个带耳罩的硅胶套,这算是一种定制,但你无法卸载预装的臃肿软件。如果你买了一套公寓,却不能扔掉不用的沙发、椅子、台灯和衣柜,你会作何感想?没错,新手机或许有一些好处。但是,你难道不怀念过去的日子吗?那时你不必查看每一条弹出的通知,不必为每一顿饭拍照,也不必第一时间获取所有新闻。

作为人类,你变得越来越懒惰,这可不是什么好事。就拿汽车来说吧。以前,你得放下手里的东西,手动打开后备箱。不仅如此,以前你还得拿出钥匙,插进点火锁,启动汽车。更糟糕的是,你还得亲自驾驶,时刻注意周围环境和交通状况。但后来,情况发生了变化,而且还在不断变化。你不再需要把钥匙插进门锁,而是有了遥控钥匙。但你又懒得按遥控钥匙上的按钮,所以现在又有了无钥匙进入系统。你真的需要用脚在后备箱底下晃晃才能打开它吗?空气悬架、电子驻车制动、坡道起步辅助、启停系统、车道保持辅助、交通标志摄像头、盲点传感器、自适应巡航控制、倒车影像、360度全景影像、各种摄像头和传感器、驾驶辅助、碰撞缓解系统、自动泊车、自动驾驶等等。你是因为懒才要求这些功能的,还是汽车制造商强加给你这些功能让你变得懒惰的?

你真的需要每隔几年就花几万欧元甚至更多去买辆新车吗?这车不就是用来从A点到B点的吗?我知道奔驰和达契亚虽然都用雷诺的发动机,不能直接比较。但关键是,花这么多钱买一堆你根本没要求的传感器值得吗?什么时候开关雨刷变得这么麻烦,以至于需要雨量传感器了?没错,汽车里确实有一些或多或少有用的新功能,但最终却让汽车变得更难、更复杂、维护成本更高,而且故障的可能性也越来越多。你难道不怀念以前自己开车、掌控车辆的日子吗?还是说,你迫不及待地想要自动驾驶汽车,这样你就可以一边开车一边盯着钱包——好吧,与其说是开车,不如说是坐在车里?

框架

既然你患有“闪亮物体综合症”(一种对新鲜事物趋之若鹜的心理) ,害怕错过什么(FOMO),而且越来越懒惰,那么一些公司和个人很容易就能说服你,你需要一些实际上并不需要的东西。他们假定你作为一名开发者很懒惰,需要一些东西来简化工作,于是创建了无数的框架和库,告诉你一天都不能少用它们。开发者和其他人一样,也会懒惰。我读到比尔·盖茨说过类似这样的话:“我会选择一个懒惰的人去做困难的工作,因为懒惰的人会找到轻松的办法。” 懒惰本身并不是坏事。但也许懒惰也有不同的类型。同一个人可能既懒惰又勤奋。开发者在学习方面绝对不懒惰。他们需要学习各种架构、技术和语言。

你不应该重复造轮子,而应该针对特定用途使用成熟的框架和库,专注于业务逻辑,而不是重复实现别人已经实现的功能。这句话既对也不对。你可能不应该自己实现某个特定的加密算法,但你应该能够编写简单的字符串填充函数。你难道不记得一位开发者是如何用 11 行 JavaScript 代码搞垮 Node、Babel 和成千上万个项目的吗?我相信你肯定遇到过这种情况:你接手了一个新项目,其中用到了某个很棒的 HTTP 库。在使用这个库之前,你不得不花费时间和精力去阅读 API 文档、查看一些代码示例等等。你完成了这个项目,然后又开始了另一个项目。另一个项目又用到了当时流行的另一个 HTTP 库。你会怎么做?你不得不再次投入时间和精力去学习如何用不同的方式做你已经知道的事情。框架也是如此。

学习用过于复杂的新方法来完成同样简单的任务,比如展示数据(大多数情况下是内容分发),以及通过网页浏览器从用户那里获取数据。在我看来,这无异于不断重复自己,重复发明轮子。先学习 X 框架、它的组件、路由、双向绑定、模板系统以及 HTTP 调用方式。然后去另一个项目学习如何使用 Y 框架,再学习 Z 框架。别被 Z 框架的名字误导,它肯定不是最后一个。之后你会发现自己还在思考如何完成最简单的任务,如何处理点击事件。是 `$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$('$(1$(( 1. ( ' $ element.addEventListener('click', ...)( 1 $( ' ...onclickng-click(click)v-on:click@clickonClick

有时候,懒惰和“闪亮新事物综合症”会驱使你投入越来越多的工作。你懒得自己创建某个组件或路由,所以你会投入时间和精力去学习和使用整个框架。但是,现在出现了一个新的框架,它以不同的方式实现相同的功能,当然也更好。它是由一家追踪用户网络行为并将这些信息出售给广告公司的公司开发的,所以它肯定很棒。需要澄清的是,我并不是说某个框架不好,仅仅因为它是由特定的公司开发的。我想说的是,它是由一家盈利的公司开发的,很可能是为了满足他们特定的使用场景,而且他们可以随意处置它。他们可以停止维护,可以在版本之间进行破坏性更改等等,而你在某种程度上却被困住了。

你最近看过前端技术方面的简报吗?你会看到诸如“使用 Framework.js CLI 可以做的 4 件超棒的事”“the-best-datatable:一款功能丰富的 Framework2 数据表组件”“some-content-loader:基于 SVG 的‘加载占位符’组件”“这款超棒的 XFramework 组件正是你梦寐以求的”之类的标题。看完这些,你可能会问自己:“我这样做错了吗?”

我的应用中需要一个功能丰富的数据表,但我使用的是 React 而不是 Angular。我可以在 React 中使用 Angular 组件吗?也许 React 有对应的组件,但它是否具备 Angular 组件的所有功能?我当初选择 React 是不是个错误?我应该用 Angular 重写我的应用吗?但是 Angular 已经更新了代码库,推出了与旧版本不兼容的新版本。如果他们再次更新怎么办?如果 React 也这样做怎么办?我还需要内容加载器。我可以在 React 应用中集成 Vue 组件吗?我看到越来越多的帖子在讨论 Vue,我应该把重点从 React 转移到 Vue,完全忽略 Angular 吗?但是 Backbone、Knockout、Ember、Aurelia……呢?我应该改用它们吗?显然,我们需要专门的框架工程师。

如今,生活中各个领域都存在着所谓的“选择悖论”,软件开发也不例外。面对琳琅满目的库、框架和工具包,你永远无法对最终选择的方案真正感到满意。你总会想:其他库是不是更快?这个库是不是太老了?GitHub 上的星标更多是不是更好?这个框架对浏览器来说是不是更轻量级?等等。

你不需要画框,你需要的是一幅画,而不是一个画框。——
克劳斯·金斯基

当你第一次决定使用一个框架时,你会受到框架的边界(形状和大小)、属性和行为的限制。这类限制有时是有益的,例如,当你的孩子想要一个新玩具时,你应该设定价格上限之类的限制。但当你创作一首歌时,你可能不应该设置和弦数量之类的限制。

使用框架时,你的思维和行为方式会被框架创建者所束缚。这或许对你来说没什么问题,但在选择框架之前,你应该三思。你是否喜欢框架的每个部分?它使用哪些库?你需要投入多少资源来学习它?你是否准备好面对它可能在几年后过时或被淘汰的现实?如果公司进行重大更改怎么办?你是否准备好每隔几个月就升级到同一框架的新版本?你选择框架是基于自身需求,还是为了适应框架而选择它?你选择它仅仅是因为现在大家都在谈论它吗?你是否准备好在必要时重写你的应用程序?

框架起初确实带来了一些好处,但同时也增加了许多复杂性。正如当今业内一位杰出的演讲者所说:“不要逃避复杂性,要​​迎难而上。”你或许一开始会认为框架能让你更快,但这并非取胜的必要条件。有时,要想成功,你需要的是专注和坚持,而不是像龟兔赛跑的故事那样,中途被其他事物分散注意力。

为了证明我并非完全反对框架,我推荐一个:Vanilla JS 。它简洁易用,安装方便,而且是目前最轻量级的框架之一

无框架

在英语中,后缀-less表示“没有”。所以,如果某人是 fearless,就意味着他勇敢无畏。如果某个事物是 serverless,则表示没有服务器,但实际上服务器是存在的,这个词用来描述一种架构概念。Frameworkless 表示没有框架,在这种情况下,你应该使用平台进行 Web 开发。

平易近人、功能全面、性能卓越、声明式、组件化、一次学习,随处编写、速度与性能兼备、强大的工具链、深受数百万用户喜爱、代码生成、代码拆分、更少的代码、模板、命令行界面、动画。这些都是用来形容某些框架和库的流行语。你能猜出哪个框架用了哪个流行语吗?听起来是不是很相似,很有说服力?

公司对其框架特性描述的准确性给予了很高的评价,这些描述据称是专门针对其自身框架量身定制的,但实际上却模糊笼统,足以适用于其他各种框架。——摘自一篇关于巴纳姆效应的文章的第一句话(部分内容)

所有曾经存在过、最终也会消亡的前端框架、库和工具包都有一个共同点:那就是 JavaScript,前端开发的演进标准。框架和库无法完成纯 JavaScript 无法实现的功能,也无法完成浏览器不支持或没有 polyfill 的功能。上述大部分(如果不是全部)流行语也同样适用于纯 JavaScript。

JavaScript 发展至今,已经拥有了 Web Components。它提供了一种创建可重用 HTML 标签的通用方法。如果使用得当,你可以在任何地方使用这些新的 HTML 标签,而无需依赖特定的框架。假设你在一个框架中创建了一个组件,那么<a-menu></a-menu>它究竟可以在哪里重用呢?只能在你的应用程序中重用,因为你无法更改菜单中的项目。但是,如果你创建了一个带有接口(可以接受一些属性)的组件,例如菜单项,那么你就可以在用同一个框架编写的其他应用程序中使用它。在一个框架中使用另一个框架的组件几乎是不可能的。但是,如果你使用 Web 标准创建组件,例如使用类似 ` <div> <a-menu><a-menu-item></a-menu-item></a-menu>` 这样的<select><option></option></select>标准,那么你就可以在任何地方使用带有特定菜单项的组件。

我说过你可以随心所欲地使用新元素。嗯,这并不完全正确,但以后会是这样。你只能在支持 Web Components 标准(Shadow DOM自定义元素HTML 模板元素ES 模块)的浏览器中使用它,对于其他浏览器,你可以使用 polyfill 来模拟标准实现。当浏览器实现对 Web Components 的支持时(而它们终将实现),你可以直接移除 polyfill,要知道,没有什么比移除不必要的东西更好了。最终,所有浏览器都会原生支持 Web Components,这是任何框架都无法做到的。

我不会详细介绍 Web Components 标准,您可以在网上轻松找到相关信息,我只举一个简单的使用示例。我不会使用 HTML 模板元素,而是通过编程方式创建元素。因此,我最终只使用自定义元素规范来定义新的 HTML 标签,通过扩展现有标签HTMLElement并创建 Shadow DOM 来封装样式和标记,从而将新的 Web Components 封装起来。

你还记得上次看到不以 `<webcomponents>` 开头的前端代码示例是什么时候吗npm install framework-with-bunch-of-dependencies-which-size-is-like-200MB?好吧,这个示例就不是,你需要的一切都已经在浏览器中了。你不需要命令行界面 (CLI),不需要包管理器,甚至不需要用之前安装的包管理器再安装一个速度稍快的包管理器。你不需要大量的复杂配置文件,也不需要任何模块打包器、构建工具、预处理器、编译器等等。唯一的外部依赖项是 Web Components polyfill 加载器及其简单的 `<webcomponents>`script元素。它会使用特性检测动态加载最小的 polyfill 包,目的是让这个示例能够在尚未支持标准实现的浏览器中运行。

<script src="https://unpkg.com/@webcomponents/webcomponentsjs@2.0.0/webcomponents-loader.js"></script>
Enter fullscreen mode Exit fullscreen mode

如果您想使用组件,只需像使用其他 HTML 元素一样使用它即可。像写标签一样写出它的名称,并定义所需的属性值。自定义 Web 组件的名称中必须包含一个短横线,以便浏览器将其与现有元素区分开来。由于自定义元素不能自闭合,因此您需要编写闭合标签。

<cn-jokes headerText="Three random Chuck Norris jokes" count="3"></cn-jokes>
<cn-jokes></cn-jokes>
Enter fullscreen mode Exit fullscreen mode

创建 Web 组件时,首先应该做的就是在 `<head>` 标签中定义它windows.customElements。这样才能让浏览器知道有了这个新元素。元素是通过继承 `<head>` 标签创建的,HTMLElement该标签包含特定的 DOM API。

window.customElements.define('cn-jokes', class extends HTMLElement {})
Enter fullscreen mode Exit fullscreen mode

类应该定义构造函数,并且在构造函数的super()第一行必须调用 `get_style()` 方法。虽然并非每个组件都必须这样做,但最好还是指示组件创建 Shadow DOM。使用 Shadow DOM 可以获得隔离且自包含的 DOM 和 CSS。Shadow DOM 中的元素和样式不会被意外访问。例如,如果您h1的 Shadow DOM 中有一个元素,而有人编写了自定义 CSS 来更改h1页面上其他元素的样式,您可以放心,这些更改不会影响您组件中的样式。

constructor() {
    super()
    this.attachShadow({ mode: 'open' }) 
    this.jokesUrl = 'https://api.icndb.com/jokes/'


    this.shadowRoot.appendChild(this.createStyleTag())

    this.refreshBtn = document.createElement('button')
    this.refreshBtn.textContent = '\u21bb'

    this.h1 = document.createElement('h1')
    this.h1.textContent = this.getAttribute('headerText') || 'Random Chuck Norris joke'

    this.header = document.createElement('header')

    this.header.appendChild(this.h1)
    this.header.appendChild(this.refreshBtn)

    this.shadowRoot.appendChild(this.header)
}
Enter fullscreen mode Exit fullscreen mode

你可以把构造函数看作是生命周期钩子,它会在元素创建时被调用,非常适合用来构建组件结构。其他钩子还有 `include`connectedCallback和 `remove` disconnectedCallback。它们分别在元素插入和从 DOM 中移除时被调用。这些钩子非常适合用来获取资源、添加事件监听器和进行清理工作。

connectedCallback() {
    this.refreshBtn.addEventListener('click', () => this.fetchAndDisplayResult())
    this.fetchAndDisplayResult()
}

disconnectedCallback() {
  this.refreshBtn.removeEventListener('click')
}
Enter fullscreen mode Exit fullscreen mode

属性用于在自定义元素中发送数据。另一个有用的回调函数(示例中未显示)是 `$(Attribute)` attributeChangedCallback。当静态属性 `Attribute`observedAttributes被更新、删除或添加时,该回调函数会被调用。如果要从自定义元素发送数据,可以使用`CustomEvents`。

this.dispatchEvent(new CustomEvent('cn-jokes-load', {
    bubbles: true,
    composed: true,
    detail: {
        jokes: jokes
    }
}))
Enter fullscreen mode Exit fullscreen mode

Web 组件的样式设置方式与任何其他 HTML 元素相同,都是使用 CSS。外部样式会覆盖 Shadow DOM 中定义的样式。要从外部设置 Shadow DOM 内部元素的样式,可以使用在 Web 组件中定义的CSS 变量。

body {
  font-family: Arial, Helvetica, sans-serif
}

cn-jokes {
  --quote-font-size: 19px;
}

cn-jokes:nth-of-type(2) {
  width: 50%;
  --header-bg-color: #FF1744;
  --quote-font-size: 12px;
}
Enter fullscreen mode Exit fullscreen mode

元素内部的 Shadow DOM 样式是通过作用于该元素的 CSS 来实现的。

createStyleTag() {
        const styleTag = document.createElement('style')
        styleTag.textContent = `
          :host {     
            display: block;
            box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
          }

          /* this is for browsers which does not support :host https://stackoverflow.com/questions/25468701/why-does-the-host-selector-only-work-in-chrome-with-platform-js#33475684 */
          cn-jokes { 
            display: block;
            -moz-box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
            -webkit-box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
            box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
          }

          header {
            align-items: center;
            background-color: var(--header-bg-color, #00B8D4);
            display: flex;
            flex-direction: row;
            justify-content: space-between;
            padding: 0 16px;
          }

          h1 {
            color: white;
            font-size: 18px;
          }

          button {
            background: none;
            border: none;
            border-radius: 50%;
            color: white;
            font-size: 24px;
            height: 32px;
            width: 32px;
          }

          button:hover {
            background-color: rgba(158,158,158,.5)
          }

          button:disabled {
            cursor: wait;
            -webkit-animation: rotation 1s infinite linear;
          }

          @-webkit-keyframes rotation {
              from {
                  -webkit-transform: rotate(0deg);
              }
              to {
                  -webkit-transform: rotate(359deg);
              }
          }

          blockquote {
            font-size: var(--quote-font-size, 14px);
            padding-bottom: 10px;
          }

          :focus {outline:none;}
          ::-moz-focus-inner {border:0;}
        `
        return styleTag
      }
Enter fullscreen mode Exit fullscreen mode

创建自定义组件时,您不再需要 CSS 框架和库,例如,您不再需要 Bootstrap 组件及其 CSS 和 JavaScript。使用CSS 弹性盒布局CSS 网格布局时,您也不需要 CSS 框架来实现布局。此外,由于 CSS 样式的作用域位于 Shadow DOM 中,您的选择器会变得更加简洁。您可以看到,我的样式中没有任何包含 ID、类等的复杂嵌套选择器。

创建自定义组件时,你应该停下来思考一下是否真的需要这个新组件。你真的需要一个button可以自定义样式的按钮组件来包裹现有的按钮组件吗?

不必要的按钮

你可能会说这样增加了语义,但真的如此吗?它仍然button使用了class属性。你应该充分利用现有元素。既然可以使用对话框元素,为什么还要创建自定义对话框呢?这完全符合 HTML 标准。

<dialog>
  <header>Dialog header</header>
  <div>Dialog content</div>
  <footer>Dialog footer</footer>
</dialog>
Enter fullscreen mode Exit fullscreen mode

创建组件时,请保持代码简洁明了。我知道 HTML 是计算机可读的,浏览器也能高效地处理它,但看到没有不必要标记的简洁 HTML 代码岂不更赏心悦目?例如,看看这段代码片段:

不必要的标记

你真的需要li先创建一个容器,然后再dv-driver-communications-flyout-item创建一个容器,然后再article创建一个容器,最后div.article-content再创建一个容器吗?

我知道,创建像数据表这样复杂的组件可能比较困难,但其他框架也一样,所以你才会使用别人创建的组件。你可以使用https://www.webcomponents.org/上现有的 Web Components 。有时候你只需要添加一些依赖项。

这是你的选择。

当某公司发布一款配备十个摄像头的智能手机时,有人会想:“这正是我梦寐以求的智能手机功能,我一定要买!而且我真的需要这款新机型配备的32GB内存。” 当某公司为汽车增加一种内饰氛围灯颜色或一种按摩座椅调节模式时,有人会想:“我那辆开了三年的车已经没用了。我想开着内饰是玫瑰色的车,同时享受座椅上百只按摩手的按摩。” 这些人总是想在所有方面都做到第一,这无可厚非。公司正是靠着这些追求最新最棒产品的人生存下去的。

当有公司发布新的框架时,总会有人愿意学习和使用它。我并不是说你不应该学习和使用框架。在某些情况下,框架确实是个不错的选择。如果你是一家小型创业公司,需要快速搭建原型,并且知道应用程序的生命周期只有几个月,那么你或许应该使用框架。但如果你是一家公司/个人,开发的是长期稳定、变化不大的产品,那么你应该掌控自己的工作,尽量减少可能导致问题的依赖项,即使这意味着你需要创建某种内部框架。

读完关于 Web Components 的文章后,你可能会觉得 Web Components 是一项崭新的技术。答案或许是肯定的,但它其实是一项标准技术。它不会像其他库和框架那样频繁更新,也不会出现破坏性变更。你可能会觉得只学习一种技术并坚持使用标准会很枯燥。也许吧,但枯燥恰恰能让你睡个好觉。枯燥也能让你有更多的时间陪伴家人朋友,做些比学习各种新框架更有意义的事情。使用稳定可靠、久经考验的“老”标准技术,其局限性和可能性都已明确,你无需担心它会轻易崩溃。你可能会说这需要编写大量的代码,没错,但你是一位专业的解决问题专家,你总能找到办法完成所有工作,并在过程中获得乐趣。

文章来源:https://dev.to/misomir/frameworkless-web-development-3n2h