剑客
关注科技互联网

对于前端组件化,你了解多少?

什么是前端组件化和模块化?

这两天一直在思考这个问题,以前对这两个概念的理解很模糊。认为“模块化”是侧重于功能或者数据的封装,目的是为了解耦合;而“组件化”更关注的UI部分,如一个页面可以分为头部、底部和内容区域等等。

这样的理解很明显是表层的简单的可能还是不正确的理解,最近反复阅读了苏宁前端“代码民工徐飞”关于组件化话的几篇文章,结合Angular1.x和Vue1.x的组件思想对组件化和模块化有了一些新的认识(原文链接在文末)。

什么是组件化

组件化的概念在后端早已存在多年,只不过近几年随着前端的发展,这个概念在前端开始被频繁提及,特别是在MV*的框架中。

前端中的“组件化”这个词,在UI这一层通常指“标签化”,也就是把大块的业务界面,拆分成若干小块,然后进行组装。

狭义的组件化一般是指标签化,也就是以自定义标签(自定义属性)为核心的机制。

广义的组件化包括对数据逻辑层业务梳理,形成不同层级的能力封装。

为什么要有组件化?

不管是前端组件化还是后端的组件化,我认为其目的无非就是为了提高开发效率和后期维护的效率。

在说的详细点,就是比如我想实现一个网站的头部,我可以把头部单独拿出来进行封装,根据不同页面或者说业务要求,可以灵活定制不同的头部(结构一致,颜色或展示等不同)。这样就可以在不同的页面进行灵活的复用,后期如果头部结构有大的变动,可以只修改该头部组件就好了。是不是这个概念很熟悉,其实你早就在这么干了,比如以前用jsp做静态页面生成的时候,就可以利用jsp的<jsp:include page="xxx.jsp"/>指令进行引入公共组件,可能也有人理解那是模板的概念。

MV*框架中的组件

大概了解了一下组件化的概念,我们来看看Vue和Angular中是怎样运用组件的思想的。

Vue中的组件

组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素, Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。

以上是Vue官网对于Vue中组件的解释,其中在徐飞的从HTML Components的衰落看Web Components的危机一文中的评论部分,Vue的作者尤雨溪(github:yyx990803)发表了对民工叔叔(我只是小菜鸟,叫叔叔没啥问题,哈哈)文章的看法,其中也提到了他对于组件的认识。

Vue中关于组件的介绍是Vue中的重点部分,从它在Vue官方文档中的篇幅就可以看出来。我认为组件中最重要的方面,到目前为止我能理解的就两部分:通讯和复用。接下来重点介绍一下Vue对于通讯的实现。

结合尤雨溪在民工叔叔文章中评论那样,组件之间的通讯可分为从内向外和从外向内两种。Vue对于这两种通信时怎样解决的呢?文档中说的很详细了,events up和props down。

从外向内的“props down”

具体来说,就是当父组件向子组件传递信息的时候,采用的是在子组件的构造对象中显示的设置props属性进行数据的传输。

Vue.component('child', {
    // 声明 props
    props: ['message'],
    // 就像 data 一样,prop 可以用在模板内
    // 同样也可以在 vm 实例中像 “this.message” 这样使用
    template: '<span>{{ message }}</span>'
})

然后向他传入一个普通字符串值:

<child message="hello!"></child>

当然为了实现字面量语法或者动态语法,可以使用v-bind在父组件上来绑定数据,详细语法请参照Vue文档,本文不做详细介绍。

需要特别注意的是:

Vue默认的是单项数据流,当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。

另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop 。如果你这么做了,Vue 会在控制台给出警告。

注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。

并且当props为应用类型的时候还可以为其添加验证。

从内向外的“events up”

Vue中默认的虽然是单项数据流,但还是可以实现子组件向父组件传输数据的,因为有些场景中,我们不可避免的会使用到。而Vue中向上传递数据采用的和Angular中一样的思想,通过自定义事件的方式实现。大体需要以下两步步:

使用 $emit(eventName)在子组件上触发事件

使用 $on(eventName) 在父组件上(或者祖先组件)监听事件

<div id="counter-event-example">
    <p>{{ total }}</p>
    //父组件监听事件
    <button-counter v-on:increment="incrementTotal"></button-counter>
    <button-counter v-on:increment="incrementTotal"></button-counter>
</div>

Vue.component('button-counter', {
  template: '<button v-on:click="increment">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    increment: function () {
      this.counter += 1;
      this.$emit('increment');//子组件触发事件
    }
  },
})
new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1;
    }
  }
})

另外Vue还可以使用slot分发内容,具体实现和更多实现请参考Vue文档。

简单描述了一下Vue的组件思想,主要目的是从民工叔叔的组件化文章中重新认识到了组件化的思想,结合Vue框架去理解,可能更容易明白民工叔叔的见解。

Angular中的组件

严格来说,Angular1.x中并没有明确提及组件的概念,只是我们可以使用app.directive()来实现自定义指令,我觉着这其实就是组件。

而Angular中实现组件之间进行通信的方式主要有四种方式:

基于rootScope的全局变量和rootScope的全局变量和scope作用域的继承性

基于作用域的继承性来实现组件通信仅限于作用域链上的通信,需要对Angular的controller之间的作用域关系特别熟悉,详细可以参考民工叔叔的AngularJS实例教程(二)——作用域与事件。

利用事件on()、on()、emit()、$broastcase()方式

先上一张图:

对于前端组件化,你了解多少?

上图中清晰的展示了Angular中利用事件去上传数据和广播事件的关系图,于是我们就可以利用事件做点事情了。

从作用域往上发送事件,使用scope.$emit

$scope.$emit("someEvent", {});

从作用域往下发送事件,使用scope.$broadcast

$scope.$broadcast("someEvent", {});

这两个方法的第二个参数是要随事件带出的数据。

注意,这两种方式传播事件,事件的发送方自己也会收到一份。

利用服务实现

利用myApp.factory()生成一个需要共享数据的对象,然后在controller中注入,就可以是获取到共享数据了,并进行修改了。

直接上代码:

var myApp = angular.module("myApp", []);
myApp.factory('Data', function() {
  return {
    name: "Ting"
  }
});
myApp.controller('FirstCtrl', function($scope, Data) {
  $scope.data = Data;
  $scope.setName = function() {
    Data.name = "Jack";
   }
  });
myApp.controller('SecondCtrl', function($scope, Data) {
  $scope.data = Data;
  $scope.setName = function() {
    Data.name = "Moby";
  }
});

组件化再思考

简单了解了一下Vue和Angular中的组件思想,我们再来回想一下组件化的概念:

狭义的组件化一般是指标签化,也就是以自定义标签(自定义属性)为核心的机制。

广义的组件化包括对数据逻辑层业务梳理,形成不同层级的能力封装。

很明显,不管是Vue还是Angular,对组件的封装都是为了对数据逻辑业务的梳理,使得不同组件各司其职,当然这其中就包括了对HTML的组件化,CSS的组件化和JS的组件化。

对于HTML的组件化可以理解为利用各种语义化标签或者自定义标签(Vue中的组件和Angular中的自定义指令等)对结构进行封装。而对于CSS的组件化,我们现在可以利用SASS或者LESS来实现,根据不同的功能对样式进行不同的封装,我觉着现在前端的一些UI框架就是对组件化的使用,去哪儿网杜瑶的Yo框架就是一个很好的例子。

至于对于JS的组件化运用,我个人觉着就是模块化。不管是CommonJS规范、AMD规范、CMD规范还是ES2015的模块机制目的都是对JS进行模块化开发,使得不同功能的逻辑或者业务分离开,每个模块专注自己的业务逻辑,这样不仅是开发的时候工作目录一目了然,后期维护的时候也能快速定位到各个业务逻辑模块。

小结

简单的对民工叔叔的几篇文章做了一下总结,其实还没有理解到位,每篇文章读了仅仅3-4遍,每读一遍都有不一样的感受,理解也不一样。可能等过段时间再去读又会颠覆我现在的认识,不管对于错,先记录一下现在的感受,后期有了新的认识再来补充或者修改。

下面推荐民工叔叔的对于组件化和Angular的一些文章,我做了少许整理,大家可以直接按照目录由浅至深的阅读原文。

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址