前端组件的复用性

前端组件化拆分的根本目标是 分而治之 的开发维护方式,复用只是第二位的需求。

UI组件复用意义并没有传统GUI软件那么大,究其原因主要有这么几个:

  1. 前端有着与生俱来的高速迭代特性。对于高速迭代的应用来说,越轻的历史包袱反倒可能带来越小的工程成本,复用组件从传统GUI开发的角度来看确实能带来不少收益,但前端是特例,那些大规模实现复用的前端系统历史包袱是很重的,在前端开发中,跨系统的组件“复制”极有可能比组件“复用”更具工程化意义。

  2. 标准互联网应用的前端开发通常是 “增材制造” 的过程。就是因为产品设计上的千差万别和前端对卓越性能的要求,使得我们在技术选型的时候会从一个小而美的框架开始,搭建适合产品的组件化系统,而不是从一个大而全的框架开始,通过“减材制造”,把它裁剪成我们需要的样子再使用。从这点上来说,越多系统可复用的组件,要么大而全,要么高度抽象,无论是哪一种结局,都没有多少工程价值。当然那些“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)

后端渲染涉及到几个工程问题:

  1. HTML直出导致样式必须前置,但组件的后端渲染发生在head标签之后,这意味着组件的css需要某种手段调整到前面
  2. 后端渲染组件的生命周期管理会变得蹩脚,目前只能放弃MVVM的方式,变成基于DOM的组件初始化方案,这个对于用惯了MVVM框架的工程师来说是一种倒退的打击
  3. 后端渲染需要改造模板引擎,对于很多团队来说,这个成本有些高,但对于模板引擎的控制是后端渲染组件化方案的必经之路。

后端渲染的组件化方案我先举个例子,以大多数人可能会熟悉的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) 的形式来加载组件,这个函数干了两件事:

  1. include组件的php文件,输出组件的HTML内容在函数调用位置
  2. 收集组件所依赖的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组件的分治要求。要在后端模板引擎中实现一个 “前端组件化框架”,这是很有趣的事。