前端组件的复用性
前端组件化拆分的根本目标是 分而治之 的开发维护方式,复用只是第二位的需求。
UI组件复用意义并没有传统GUI软件那么大,究其原因主要有这么几个:
前端有着与生俱来的高速迭代特性。对于高速迭代的应用来说,越轻的历史包袱反倒可能带来越小的工程成本,复用组件从传统GUI开发的角度来看确实能带来不少收益,但前端是特例,那些大规模实现复用的前端系统历史包袱是很重的,在前端开发中,跨系统的组件“复制”极有可能比组件“复用”更具工程化意义。
标准互联网应用的前端开发通常是 “增材制造” 的过程。就是因为产品设计上的千差万别和前端对卓越性能的要求,使得我们在技术选型的时候会从一个小而美的框架开始,搭建适合产品的组件化系统,而不是从一个大而全的框架开始,通过“减材制造”,把它裁剪成我们需要的样子再使用。从这点上来说,越多系统可复用的组件,要么大而全,要么高度抽象,无论是哪一种结局,都没有多少工程价值。当然那些“CRUD管理后台类应用”由于没有GUI方面的产品特异化需求,或许并不在意这个问题
分而治之真的是一个非常重要的编程理念,把复杂庞大的问题化解为一个个独立的单元逐个解决,最终让这些单元组合得到原来的复杂系统。前端这种GUI软件,其分治的单位理所当然的会成为UI组件。
另外,很多人在这里讨论了组件化的具体实现形式,这里我也想分享一些经验:
前端组件化方案其实有两大类:前端渲染(JS First) & 后端渲染(HTML First)
前端渲染组件化方案(JS First)
简单点说,就是把HTML片段(前端模板)和CSS注入到JS中使用,最后通过JS的模块化加载与管理实现界面生成,整个过程是完全由前端控制的。React/Angularjs/Vue等MVVM框架都属于这一类的方案。好处是前端控制力强,还能进行所谓的“前后端分离”,基本架构设计是这样的:
site
├─ components
│ ├─ nav
│ ├─ header
│ │ ├─ header.js
│ │ ├─ header.css
│ │ └─ header.html
│ └─ ...
└─ pages
├─ index
│ ├─ index.html
│ ├─ index.css
│ └─ index.js
└─ ...
其中,components/header/header.js的代码(我以Vue为框架做为例子)为:
require('./header.css'); //声明对css的依赖
module.exports = Vue.extend({
template: require('./header.html'), //引入模板
components: {
'x-nav': require('nav') // 组合其他组件
}
});
components/header/header.html的代码为:
<header class="header">
<div class="header_logo">...</div>
<x-nav></x-nav> <!-- 在header.js中注册的组件标签 -->
...
</header>
components/header/header.css的代码为:
.header { ... }
.header_logo { ... }
.header .nav { ... } /* 对子组件进行一定的样式覆写 */
这样,就用纯前端的形式组织了一个UI组件,HTML和CSS揉入到JS中,最终在页面上这样使用(pages/index/index.html):
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
...
<div id="header"></div>
...
<script src="xmd.js"></script>
<script>
require('header', function(Header){
new Header({
el: '#header' //将组件渲染到页面上
});
});
</script>
</body>
</html>
以上就是前端渲染组件化方案的实现,前端渲染比较适用于工具类的前端应用,比如管理后台、地图、todolist等等,特点是RIA,重交互。纯前端渲染方案基本上都会实现为SPA,当然,也有它的局限性,就是SEO不友好,不适合很多内容型的web应用,正如 @lifesinger 所说,不太适合他们的产品。
后端渲染组件化方案(HTML First)
后端渲染涉及到几个工程问题:
- HTML直出导致样式必须前置,但组件的后端渲染发生在head标签之后,这意味着组件的css需要某种手段调整到前面
- 后端渲染组件的生命周期管理会变得蹩脚,目前只能放弃MVVM的方式,变成基于DOM的组件初始化方案,这个对于用惯了MVVM框架的工程师来说是一种倒退的打击
- 后端渲染需要改造模板引擎,对于很多团队来说,这个成本有些高,但对于模板引擎的控制是后端渲染组件化方案的必经之路。
后端渲染的组件化方案我先举个例子,以大多数人可能会熟悉的php为模板,架构设计与前端渲染方案非常相似:
site
├─ components
│ ├─ nav
│ ├─ header
│ │ ├─ header.js
│ │ ├─ header.css
│ │ └─ header.php
│ └─ ...
└─ pages
├─ index
│ ├─ index.php
│ ├─ index.css
│ └─ index.js
└─ ...
只不过是HTML片段由原来的.html文件变成了.php文件,其中 components/header/header.php的代码为:
<header class="header">
<div class="header_logo">...</div>
<?php require_component('nav'); ?> <!-- 加载其他组件 -->
...
</header>
模板方面我们只是把原来的自定义标签变成了一个模板函数 require_component($component_name) 的形式来加载组件,这个函数干了两件事:
- include组件的php文件,输出组件的HTML内容在函数调用位置
- 收集组件所依赖的JS/CSS资源,存到一个数组里
header组件的CSS代码保持不变,JS代码由于没有了渲染页面的职责,也可以去掉了。然后,我们的 pages/index/index.php 内容也会发生改变:
<!DOCTYPE html>
<html>
<head>
...
<!-- STYLE_PLACEHOLDER -->
</head>
<body>
...
<div id="header">
<?php require_component('header'); ?>
</div>
...
<!-- SCRIPT_PLACEHOLDER -->
</body>
</html>
在页面上增加了两个注释, <!-- STYLE_PLACEHOLDER -->
和 <!-- SCRIPT_PLACEHOLDER -->
这样,我们可以把require_component函数收集到的组件依赖CSS和JS在模板完成渲染前替换到这两个注释占位的位置,实现组件化资源加载,而页面是后端组件化拼装输出的,样式和脚本也实现了组件化维护。
个人觉得,这种组件化方案在现阶段要比webcomponents更具有可实践性,而且开发成本更低,兼容性更好,也保证了UI组件的分治要求。要在后端模板引擎中实现一个 “前端组件化框架”,这是很有趣的事。