这是一个前端框架满天飞的时代,你尚未学会其中的一个,新的又出现了。更糟糕的是,你花大力气所学习到的内容可能已通过时了。虽然说学习是一生的事,但在软件开发领域,你不能太盲目,你必须要有是非分明的能力以及快速学习的能力。是非分明有助于淘汰那些不值得学习的目标,快速学习能够避免学完就过期的尴尬。对于大部分人而言,这些能力是难以在短时间内习得的,这须要长期的实践、思考与总结。本人使用过很多框架,也写过框架,有一些这方面的心得体会,现分享出来,但愿对于众多的初学者能有些许的启发,尽可能少走些弯路。固然,这些只是我的的一些认识,若有不当或者争议之处,欢迎探讨。css
这篇文章虽然说是讲前端框架的,但其中的许多内容具备普适性,对于后端也是适用的,你能够把其中的观点天然地迁移到你所熟悉的后端编程语言中。为了不陷于介绍繁琐的基础细节,这里假定读者对于面向对象程序设计已经有了必定的认识。html
在继续进行下面的内容以前,须要先给出两个事实。后面对于一些问题的处理均可以在这两个事实中找到根据或者动机。前端
咱们难以认知或者构建无序的复杂的软件系统vue
咱们有办法认知或者构建有序的复杂的软件系统编程
这里有必要对 无序
与 有序
做一些说明。无序的软件系统是指无章法的,没有依赖成熟的理论方法构建的软件系统。而有序的软件系统则相反,它相身是有明显的结构而且是易于理解的。另外,这里的 复杂
主要指的软件系统规模达到必定的水平。后端
从这两个事实出发,如今咱们能够给出一个理想的框架所能实现的目标:能够经过该框架构建有序的复杂的软件系统,或者说经过该框架构建的复杂的软件系统是易于认知的。sass
既然讲的是与前端框架相关的内容,那么咱们有必要从新审视下,在无框架使用的条件下,对于传统网站页面,咱们是如何构建的。咱们通常会这么作,在 html
文件中写上众多的标签和内容。而后,通常状况下还会有一些相关 js
文件与 css
文件。js
文件与 css
文件用于操做或者描述 html
文件中的内容。前端框架
上面一段讲述的页面构建方案,如今还有许多人在用,对于简单的页面尚可,但若是所要构建的页面足够的复杂,对于页面的维护会是一个不小的负担。这主要体如今下面两个方面:首先,大量存在的 id
属性值或者类属性值容易致使命名冲突;其次,在添加新功能或者扩展新功能时,将难如下手。架构
从上面的分析,咱们能够看出经过传统方式来构建复杂的页面是行不通的,那么问题出在哪里?若是你将上述页面的总体结构与面向对象程序设计中类的结构作个对比,你会发现,该结构有数据对象(html 标签与内容)、有操做(js 代码与 css 描述)。这显然就是一个 类
嘛!因此问题的关键在于它把全部的逻辑都写在一个 类
里了。因此,解决这个问题的思路是分解并重组该 类
,下降构建的复杂度。这样咱们就获得理想框架应该有的第一个功能:提供面向对象编程语言里面类的结构来分解复杂的目标系统。在前端,这种结构通常被叫作组件。框架
目前,已经有很多框架就上述问题作了尝试,React 是其中的一个表明,它经过 React.createClass 来建立组件。以下面的示例所示:
var Hello = React.createClass({ render: function() { return <h1 className='hello'>Hello World!</h1>; } });
React 的组件化方案并不完美,这主要体如今对样式的处理上,它并没解决全局样式的冲突问题。大部分的人仍然经过命名约定或者使用 less/sass
来实现曲线救国。虽然有一些 CSS in JS
的第三方方案,但解决起来总以为很是别扭。不过 CSS in JS
的方案却为咱们对问题的解决打开了一道窗。下面是 xmlplus 的解决方案,堪称完美。
Hello: { css: `#hello { color: blue; }`, xml: `<h1 id='hello'>Hello World</h1>`, fun: function(sys, items, opts) { sys.hello.css("font-size", "28px"); } }
理解该示例的关键在于视图项中 id
标识符。它是局部的,仅在组件内部可访问,在函数项中对其操做与在样式项中对其描述,本质上没什么差异,最终都是对目标对象施加影响。这种处理方式是优势远不止对上述问题的解决,更多内容能够访问文档的相关章节。这一节请注意 局部化
思想的应用,后面内容将反复使用它来解决相关的问题。
回到页面的设计,当咱们对一个页面进行组件化分割后,原来页面内能够直接通讯的对象有可能被割裂开来。一个理想的前端框架应该可以提供修复这种通讯关系的能力,这就是组件之间的通讯。
按不一样组件的关系划分,组件之间的通讯能够分为父子组件和任意组件之间的通讯。其中父子组件之间主要经过事件的传递和直接可见的接口来通讯,这一方面不少主流框架都会提供,因此这里将集中注意力讨论的任意组件之间的通讯机制。相对于任意组件之间的通讯,比较遗憾的是目前主流的框架都在努力实现数据的绑定来避免操做 DOM 就能更新视图之类非必要的功能。
首先,明确一点,一个应用就是一棵组件树。为方便起见,下面以 xmlplus 中的例子来讲明,固然,你也能够联系到 React 或者 vue 中的组件树。
Example: { xml: `<div id='index'> <Hello id='foo'/> <World id='bar'/> </div>`, fun: function(sys, items, opts) { console.log("communication test"); } }
此示例经过两个组件节点 Hello 与 World 之间的通讯来演示任意组件之间的通讯。上述 Hello 组件与 World 组件的内容以下:
Hello: { xml: `<button id='hello'>Hello</button>`, fun: function(sys, items, opts) { this.on("click", e => this.notify("hello", "msg")); } }, World: { xml: `<h1 id='world'>World</h1>`, fun: function(sys, items, opts) { this.watch("hello", (e, msg) => console.log(msg)); } }
观察此组件 Index,咱们能够分解出以下的组件树:
Example/ └── div[index] ├── Hello[foo] │ └── button[hello] └── World[bar] └── h1[world]
若是只实例化一个 Example 组件上述示例能很好的工做,但若是实例化多个 Example 组件就会出问题了。咱们来看看具体的示例:
Index: { xml: `<div id='index'> <Example id='foo'/> <Example id='bar'/> </div>`, fun: function(sys, items, opts) { console.log("two Examples"); } }
该示例中,点击其中一个 Hello 按钮,将致使全部消息侦听器接收到消息,这显然不是咱们想要的。因此咱们须要对消息进行局部化。请看下面改进后的 Example 示例,改示例在映射项中配置了一个值为 true
的 msgscope
选项,这将致使当前组件及其子组件消息通讯的局部化。局部化的区域能够参考上面给出的组件树视图。
Example: { xml: `<div id='index'> <Hello id='foo'/> <World id='bar'/> </div>`, map: { msgscope: true }, fun: function(sys, items, opts) { console.log("communication test"); } }
通讯的局部化带来两个好处,一个是避免消息污染,另外一个是当一个应用足够复杂时,全局的通讯将不可避免地陷入混乱。通讯的局部化的想法与组件思想一模一样,都是为了应对构建复杂系统服务的。
目前主流的框架并不提供组件之间的通讯功能,同时第三方的通讯软件包通常也不会提供通讯的局部化功能。为了应对构建复杂的系统,你能够在消息的命名上做文章,尽管麻烦一点。
本文讲了一个理想框架应该包含两个最基本的功能,一个是优秀的组件化能力,另外一个局部化的通讯功能。前者在不少面向对象编程语言中都会包含,后者通常只能实现全局的通讯功能,但读者应该有局部化的通讯意识。
下一章咱们将讨论命名空间、组件对象共享以及延迟实例化等锦上添花的框架应该提供的功能,敬请期待。