上一篇博文梳理了vue的数据驱动和响应式相关的特性,这一篇博文就来梳理vue的一个很重要的特性,组件化。
自定义组件之于vue,其意义不亚于函数之于C,java之类的编程语言。
函数是计算机科学中的一大重要的发明。
一方面,它表明着一种自顶向下,逐步求精的分而治之的思惟,另一方面,它可以封装复杂实现的细节,提供更高抽象的接口,下降软件工程的复杂度。html
在vue中,自定义组件也起着相似的做用。vue
<!--more-->
咱们知道,在组件化的GUI界面上,GUI能够被视为一棵树,浏览器的DOM就是一个最好的例子。
从布局上来看,界面能够当作大盒子套小盒子,小盒子再套更小的盒子。。。
反映到DOM上,DOM某节点的全部子节点,都是该组件的子组件,都是该组件内的元素。java
在vue中也是如此,vue组件之间的关系也是相似DOM同样,是树状的。
在定义一个组件时,须要引用的全部组件,都成为了该组件的子组件。git
组件做为一个模块性质的东西,天然就有着它必定的独立性。并且,与其它模块的耦合都理所应当的有着明确的接口约定。
在vue中,父子组件通讯经过组件属性和事件来进行的。
其中,经过组件属性,父组件的数据流向子组件;经过事件,子组件的数据流向父组件。github
从抽象的角度看,组件做为一个黑盒子,它有着特定的属性用以接收外部传递给它的数据,它也有着特定的事件,当特定操做发生时调用回调函数,以通知别的组件。编程
<div id="x-child"> <span> {{ titleMessage }} <span> </div> <script> Vue.component('x-child', { template: '#x-child', props: ['titleMessage'] }); </script>
<div id="x-parent"> <x-child title-message="A"></x-child> <x-child title-message="B"></x-child> <x-child :title-message="message"></x-child> </div> <script> Vue.component('x-parent', { template: '#x-parent', data: function () { return { message: "C" }; } }); </script>
上面的例子中,定义了x-child自定义组件,而且在x-parent组件中引用它。
以前在介绍数据驱动的时候,咱们知道,定义vue组件时,能够经过data定义组件内部的状态,它是组件数据的一部分。
除了data以外,prop(属性)也是组件数据的来源之一,父组件经过prop将本身的数据传递给子组件。segmentfault
在定义组件时咱们能够看到:数组
在使用自定义组件时能够看到:浏览器
vue中,属性被设计用于父组件传递数据给子组件的,若是子组件改变了属性,那么父组件不会受到任何影响。这在vue中被称为 单向数据流。
可是,若是属性用来传递数组或对象等复合的数据结构,那么可能会出问题。考虑如下的场景:数据结构
问题在于,因为js是引用类型语言,简单的赋值仅仅是传递引用,那么,以上场景中,父组件中的数据,子组件中的属性,还有子组件中的状态, 指向的都是同一份对象 !
这会形成一个问题,若是子组件修改了该对象的属性,那么父组件的数据也会受到影响,这破坏了单向数据流,会形成不少诡异的bug。
解决方法也很显然:
<div id="x-child"> <button @click="onClick">click</button> </div> <script> Vue.component('x-child', { template: '#x-child', data: function() { return { counter: 0 }; }, methods: { onClick: function() { this.counter++; this.$emit("on-counter-add", this.counter); } } }); </script>
以上自定义了x-child组件,而且自定义了组件事件。咱们能够看到:
<div id="x-parent"> <x-child @on-counter-add="onCounterAdd"></x-child> <span> { { counter }} </span> </div> <script> Vue.component('x-parent', { template: '#x-parent', data: function () { return { counter: 0 }; }, methods: { onCounterAdd: function (counter) { this.counter = counter; } } }); </script>
以上定义了x-parent组件,而且引用了上面定义的子组件。能够看出:
在监听事件的地方,上面的写法是使用了一个回调函数,不过,也可使用js表达式,好比:
<x-child @on-counter-add="counter = arguments[0]"></x-child>
上面代码的重点在于arguments[0]
,若是是js表达式写法,使用arguments引用事件的参数,就好像这段js表达式被放入了一个vue提供的匿名函数,而后使用匿名函数监听这个事件同样。
那它有什么用呢?在上面的场景里这样写固然是很差的,由于削弱了可读性。
以前在我同事碰到的一个场景里,是一个涉及到插槽分发做用域的场景,若是写成回调函数的形式,那么在回调函数中没法访问插槽做用域的变量。
所以,必须使用js表达式的写法,将插槽做用域中的变量显式的带到回调函数中,代码相似这种,懒得构造具体的例子了 :
<x-child @on-counter-add="onCountAdd(arguments[0], scope.id)"></x-child>
因为vue设计的父子组件通讯是单向数据流,可是因为一些需求的须要,若是能提供双向数据流,会使使用起来更方便。
便捷性和设计的统一性冲突,怎么办?固然是用语法糖解决了。
实际上,vue提供的两种好像是双向数据流的机制,.sync
和 v-model
,都是语法糖。
<comp :foo.sync="bar"></comp>
这种写法只是下面的语法糖:
<comp :foo="bar" @update:foo="val => bar = val"></comp>
子组件内,若是修改了foo时,须要触发update:foo
事件。
v-model经常使用于相似表单这样的自定义控件:
<my-checkbox v-model="foo"></my-checkbox>
它也是以下语法的语法糖:
<my-checkbox :value="foo" @input="val => foo = val" > </my-checkbox>
仔细思考刚才的自定义组件的定义,不难发现,上面的自定义组件只能对DOM中的一棵子树作抽象和封装。
那么,考虑这样一种状况,咱们封装了一个card组件,card的内容可使用任意的vue组件填充。
这种场景,就须要在自定义组件时,可以在组件的DOM树里 挖个洞 ,这个洞可以让该组件的调用者填充。
vue提供的这种相似的机制,被称为插槽。
<div id="x-my-card"> <h2>我是子组件的标题 <slot name="title"></slot> </h2> <slot> </slot> </div> <script> Vue.component('x-my-card', { template: '#x-my-card' }); </script>
<div id="x-component"> <x-my-card> <p>这是一些初始内容</p> <p>这是更多的初始内容</p> </x-my-card> <x-my-card> <h2 slot="title">标题</h2> <p>这是一些初始内容</p> <p>这是更多的初始内容</p> </x-my-card> </div> <script> Vue.component('x-component', { template: '#x-component' }); </script>
从上面的示例中能够看到:
slot
标签给自定义组件留了一个“洞”。slot
标签的name
属性定义插槽名称以区分不一样的插槽,这样可以在自定义组件上挖多个”洞”。vue提供的插槽机制,在给自定义组件挖”洞”的同时,还能使自定义组件给洞里填充的组件传递数据。以下:
<div id="x-my-card"> <slot text="hello from child"></slot> </div>
<div id="x-component"> <x-my-card> <template slot-scope="scope"> <span>{{ scope.text }}</span> </template> </x-my-card> </div>
从上面能够看出:
slot
时,能够经过属性将数据传递给它。在引用自定义组件的地方,将插槽内容放入template
标签内,经过slot-scope
指定变量名,便可在template
标签内引用该变量从而使用插槽传递过来的数据。本篇博文梳理了vue的自定义组件机制,经过自定义组件,就可以在vue项目中很好的将项目组件化。
一方面,可以提取共同的组件进行复用,下降代码冗余;另一方面,也可以提供一种强大的抽象机制,提升vue的表达能力。
注:该文于2018-04-10撰写于个人github静态页博客,现同步到个人segmentfault来。