组件化 or 分层

@xufei 2018-12-11 14:55:08发表于 xufei/blog

从大的方面说,我是一个很坚定的分层党,而不是组件化党。

在之前这篇中,我详细阐述过这方面的看法,到现在回头看,里面有些细节待修补。大致总结一下观点如下:

  1. 组件化的最大意义在分治,而不是可复用
  2. 细粒度的东西,或者不带业务含义的东西,我倾向于组件化
  3. 大粒度的东西,不管有没有业务含义,都比较倾向于模板化

这两者的区别在于复用方式。

对于组件来说,我们期望的复用方式是传递不同的参数,以达到不同形态和行为。我们需要在编写它的时候,审慎地考虑参数的设定,并且,在使用的时候,需要考虑版本变更所带来的影响。比如说,修改了一个组件,这个修改会传导到所有使用了它的链路上,因此,你的修改会需要作“是否兼容”的判断。

对于模板来说,它并不存在”复用“这个事情,我们只把它视为一次性的,如果说这个上面想要存在什么复用,我们用 snippets 去承载它。一旦一个模板被创建,它的变更仅影响自身,没有依赖链路。

仅从上面的内容来看,这些观点显得可能很非主流,甚至看上去跟我自己早先的一篇回答都是不兼容的,而且,我们通常都认为,组件化是批量制造的基础。那么,为什么我会有这样的观点呢?

这取决于我们对“模板”这个东西的认知。

从使用代价的角度看,一旦你在比较大的组件上追求可复用性,那必然会面临这样的问题:

越是大粒度的东西,要满足不同情况,必定会有越多的可配置项。一旦你的配置项多了,单个组件搞出 10 个以上的属性,或者有些配置项很复杂了,比如要搞得像 React 的 renderProps 那样,这种挖了很多洞的组件,跟不组件化到底有什么区别?

const A = (props) => {
  const { foo } = props
  return (
    <AAA>
      { foo() }
    </AAA>
  )
}

const B = () => {
  const renderFoo = (
    <BBB />
  )

  return (
    <A foo={renderFoo} />
  )
}

const C = () => {
  const renderFoo = (
    <CCC />
  )

  return (
    <A foo={renderFoo} />
  )
}

仔细想想,大部分场景下,它们真的一定要组织成这样吗?你为了把 A 弄成一个可复用的东西,所以要这么绕一圈,但如果你从来不把 A 视为一个可复用的东西,而是把其内容拿出来作为 snippets,用的时候在 B 和 C 里各写一份,到底会影响什么呢?

所以,很大程度上,如果你过于追求在大粒度组件上的可复用性,一定会导致抽象上的高复杂度,在这个层级的抽象,其实意义很小,还因此引入了一个依赖树的管理复杂度,性价比不高。在业务上做抽象的时候,需要慎重地权衡组件抽象所引入的代价。

从更大的角度看,如果一个组件存在非常多的可配置项,那就等同于把所有配置项及其变更逻辑抽离到一个承载容器中,剩下的部分只负责渲染。

  • 在 AngularJS 中,这个部分是 controller
  • 在 Vue 中,是 Vuex 之类
  • 在 React 体系中,是 mobx,unstated,或者 redux
  • 在 Angular 中,是“组件类”自身,或者可以以其他形态承载

所以,从这个角度看,真正的视图仍然是分层的,分出来的那个展示部分都可以泛指为模板。至于分层到什么形态,是函数式、流、OOP,那都是各框架自身所倾向的方法论,并不影响把视图隔离出去这么一个明显“分层”的动作。

此时,视图不过是逻辑容器的附属物,可以视为是一种配置而已。

在本文前面所提到的“模板”,都是泛指,不限于静态的类似 XML 写法表达的东西,或者是包含 JavaScript 逻辑的 JSX 写法。

下面,我们顺带探讨一下“模板式写法”,从这里开始,提到的“模板”都是这个含义了。

很多人感觉,模板这个东西,在组件化体系里显得很违和。这取决于你从什么角度理解了。

注意下面这个论断:

模板是组件化的配置式便捷写法。

如果从这个角度去看,那“模板”这个东西在组件化体系里就是自洽的了。我们整体上仍然是以组件化的方式来生产软件,只是在写视图的时候,采用了一些声明式的配置来简化其描述过程而已,并不会因为这么做了,就导致组件化的大方向出现偏差。

不仅如此,我们还发现了,如果以这种方法论来进行业务软件的前端开发:

  • 细粒度作为黑盒组件
  • 逻辑分层、集中管理
  • 大面积的视图(包括页面)模板化

之后,我们可以很便利地做可视化的页面搭建系统:

  • 通过组件引入机制,引入细粒度的基础组件
  • 开放受管控的代码编写机制,编写我们的逻辑层
  • 可视化或者通过模板编写大粒度视图,甚至页面

整个过程,彻底绕开了复杂的技术难点(你想自己解析 JSX 吗?),而且,在产品实现上也能够很平衡。在一个相对稳定的体系中,将视图和逻辑分开进行管控和复用,可以营造出门槛很低的可视化开发系统。

从整个体系来看,需要编写代码的那种组件在平台上被视为黑盒(或者是一个无限小的质点),大粒度的业务视图看作大块的平面,逻辑与数据成为骨架。整个业务系统的开发过程就是:

在骨架上铺设表皮,表皮上嵌入适当的瓷砖或者马赛克(组件)

[ 组件1   组件2 ] <模板1>       [ 组件3 ]<模板2>
-----------------------------------------------
                 前端的逻辑容器

如上所示:分层与组件化同时存在,组件化不是一个直接切到底的东西