脱离前端框架,回归本源的探索

1,361 阅读8分钟

这个话题很有趣,但是往往它的结论会惹人非议,不管它的结论是什么

笔者在编写 Vanilla JS 相关项目的时候,在思考为什么 React、Vue等前端框架大行其道,它们究竟给我们带来一些什么? 它们最终会被什么取代?

我记得刚学习 React 的时候,对它的理解较浅,认为React最大的优势是组件化。

后来稍微深入一些,认为React的优势是虚拟DOM,虚拟DOM不但接管了我们对DOM的描述,也帮我们集中的操作DOM,由于开发者描述UI的代码被接管,顺带赋予了其他能力,如React-Native等其他渲染层的实现。

再后来觉得React真正带来的是声明式,这种方式比起传统命令式的开发UI会更好的响应复杂状态的变化。

再后来觉得 React 带来的是一个统一的编码习惯,是一个业内规范,任何人去到任何公司的任何项目,他们已经在一个大的规范下进行工作,这样可以减少各方成本。

随着时间的流失,发现其实前端这些年真正变化的是工程化,React只是让笔者加速拥抱了前端工程化,越发觉得前端真正的改变并不是这些框架,而是webpack、babel(es-next)、polyfill等其他工程相关的事情,有了它们我们能做很多,React也是这趟列车上的一个过客。

我们反思,如果没有React、没有JSX我们能完成同等质量的项目么?答案是可以的,即便我们使用 document 原生API,去创建Element,去append Element树,然后通过一些简单的设计模式如发布订阅来更新Element,我们一样能实现React相同质量的项目,最终的项目体积会更小,性能更高, 我们知道虚拟DOM的最终目标也只是无限接近 Vanilla JS。

但是如果失去了Webpack、rollup、babel、eslint、jest 等工程辅助相关的工具,我们会寸步难行。

React的确非常好,但是这世间万物都是有舍才有得;React声明式的编程方式优势不再复述,缺点也很明显:diff算法帮我们做了大量非有效计算。

如果我们需要一个极限性能的Web-App,我们应该使用 Vanilla JS,这个时代我们已经了有工程化的所有工具,下一个阶段应该是返璞归真。

Vanilla JS 缺少什么

我们需要一些基础组件,并不是具体UI级别的组件,而是类似于 styled-components、react-window、redux、react-router 等非业务相关的组件。它们对完成一个 SPA 应用是必不可缺的,之后才是一些必须会用到的复杂业务组件,如表格、markdown编辑器等等。

不过历史往往就是这样,真正正确的东西往往就在目的地等着我们。所有没有使用UI框架、jQuery等类库的JS组件其实都是 vanilla 组件,它们往往维护了许多年,从jQuery时代开始,它们就在那里,React时代虽然他们消亡了许多,但是等React、Vue浪潮退去,还在更新的这些组件,往往是刚需且通过众多项目检验的。

而我们经历了现代前端框架的人,已经习惯于现代的前端开发方式,我们需要路由、状态管理等等一系列工具。

在此思考的基础上,笔者使用创建了一系列零依赖的基础库,它们非常轻量,普遍体积在1-2kb之间,却可以很好的组合,并且全部使用 Typescript 编写,有着很好的编辑器提示:

github.com/ymzuiku/van…

vanilla-document 是一个使用链式声明UI的框架,它能让我们像写JSX一样的使用 document.createElement 声明DOM树。它的体积较大,gzip 之前有 5kb。

github.com/ymzuiku/van…

vanilla-route 是一个类似于 react-route 的路由,但是它不依赖于任何框架,并且极其轻量和移动优先;所谓移动优先是指,它会保留历史页面,已减少移动端我们返回历史页面所需要做的保留滚动高度、重新渲染历史状态等一系列开销。它的体积较大,gzip 之前有 5kb。

vanilla-route 支持类似于 react-loadable 的异步路由加载方式,并且可以设定某些路由在页面渲染后的若干秒预先加载,减少次页白屏的时间。

github.com/ymzuiku/van…

vanilla-observer 是一个状态管理工具,它吸取了 react-hooks 中memo的一些校验规则,来减少事件派发。并且可以监听 Element,并派发任务;当 Element 从 document 移除后,它会自动取消相关的事件监听。

github.com/ymzuiku/van…

vanilla-http 是一个和 axios 类似的 http 请求库,不过它更加轻量,并且简化了返回的数据。如果你对最终的 SPA 体积有着严格要求,它或许适合你。

github.com/ymzuiku/van…

vanilla-list 是一个懒加载的List,用来解决大列表渲染性能问题,对于无限长的列表,它不如 react-window 虚拟列的实现方式性能高,但是在90%的场景它性能足够,并且布局更简单和随意(不需要计算行高)。

github.com/ymzuiku/van…

vanilla-icon 是一个用于渲染字体图标的组件,组件非常简单,仅仅是一个函数,返回的是一个Element,支持 iconfont 和 svg 两种格式。

github.com/ymzuiku/van…

vanilla-message 是一个用于在屏幕中显示消息的组件,不管在任何框架中,我们只需要执行一个函数即可达到目的。消息出现、消失的动画是基于弹性的(vanilla-spring)。

github.com/ymzuiku/van…

vanilla-device 是一个用来统一各设备一致性的库,它的目的是让我们的设备渲染的Web更接近原生;我们通过它可以获取一个设备类型、折本的安全区域(如iPhoneX的底部)、解决设备键盘弹出点击空白收回问题、解决设备滚动区域冒泡至document.body的问题、解决设备双击缩放、双指缩放等问题。

github.com/ymzuiku/van…

vanilla-style 仅仅是一个用来设定 Element Style 属性的方法,我们使用 Vanilla JS 编写 UI,设定 Style 往往需要写非常多的代码,它只是简化这个行为的糖果函数。

github.com/ymzuiku/van…

vanilla-cssjs 是一个在 Javascript 中编写 CSS 的工具集,它给出了一个更巧妙的方式来编写 BEM 风格的 CSS

github.com/ymzuiku/van…

vanilla-spring 使用 keyframes 来实现高性能弹性动画,使用它我们可以编写更接近现实物理运动的动画,并且借助 CSS 的渲染性能,而不需要使用 JS 高频操作 DOM。

笔者即便在一个项目使用所有以上vanilla库(包含路由、状态管理、动画等等),最终产生的 JS 包仅有10kb,配合 vanill-route 的路由拆分,我们可以把一个项目首屏的JS包控制在 50kb 以下,基本达到秒开。

这些只是笔者的一些探索,思考 -> 探索-> 思考,我们有了以上一些列的工作,现在合适重新思考我们的题目。

Vanilla JS 还需要什么

除了笔者开发的这些库,正如我们之前的观点,Vanilla 的库其实已经非常丰富了:

我们可以看到,光 vanila 关键词的 Javascript 项目就有 1.4 w个。所以我们看得出来,一个 Vanilla JS 项目真正缺少的不是生态库,而是缺少规范。

Vanilla JS 非常自由,你可以用Web开发的所有可能的技巧和路径开发你的项目,所以它最需要的是的可实践性高的规范。

这正是现在框架大行其道的真正原因,并不是它多完美,而是它这现阶段已然是一个行业规范。预测,未来 Vanilla JS 会有着属于它自己的若干规范,这是另一个范畴,未来有机会再讨论。

框架的最后就是无框架

我们可以看到使用 Vanilla JS 创建的项目非常有生命力,正如笔者创建的那些库,它可以在任何框架、任何项目中执行,而且不需要去追随框架的版本进行维护。如果一个库依赖于框架,你就需要随着框架的更新迭代去迭代那个库。

随着未来前端微服务兴起,我们会需要越来越多的跨框架库,它们基本就是 Vanilla JS 编写的,当这些库足够多了,我们只需要一个规范,就可以使用他们很好的完成项目,框架也就不需要了。

当然,以上仅是笔者的思考,欢迎善意的讨论 :)