本文的Demo和源代码已放到GitHub,如果您觉得本篇内容不错,请点个赞,或在GitHub上加个星星!
https://github.com/zwl-jasmine95/Vue_test
以下所有知识都是基于vue.js 2.0版本
一、组件编译作用域
<child-component> {{ message }}child-component>
message
应该绑定到父组件的数据,组件作用域简单地说是:
父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。
组件的模板是在其作用域内编译的,那么组件选项对象中的数据也应该是在组件模板中使用的。
1 <div id="component-demo"> 2 3 <hello-component>hello-component> 4 div> 5 6 <script type="text/javascript"> 7 Vue.component('hello-component',{ 8 template:'hello component!
' 9 }); 10 var vm = new Vue({ 11 el:'#component-demo' 12 }); 13 14 script>
在创建一个Vue实例时,除了将它挂载到某个HTML元素下,还要编译组件,将组件转换为HTML片段。
除此之外,Vue实例还会识别其所挂载的元素下的
实际上浏览器仍然是不理解
组件在使用前,经过编译已经被转换为HTML片段了,组件是有一个作用域的,那么组件的作用域可以将它理解为组件模板包含的HTML片段,组件模板内容之外就不是组件的作用域了。
例如,hello-component组件的作用域只是下面这个小片段:
通俗地讲,在子组件中定义的数据,只能用在子组件的模板。在父组件中定义的数据,只能用在父组件的模板。如果父组件的数据要在子组件中使用,则需要子组件定义props。
二、使用slot分发内容
1、什么是“内容分发”?
在使用组件时,往往会这样:
<app> <app-header>app-header> <app-footer>app-footer> app>
注意两点:
-
组件不知道它会收到什么内容。这是由使用
的父组件决定的。 -
组件很可能有它自己的模版。
为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为 内容分发。Vue.js 实现了一个内容分发 API,参照了当前 Web 组件规范草案,使用特殊的
元素作为原始内容的插槽。
2、单个slot
1 DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>单个slottitle> 6 <script type="text/javascript" src="../lib/js/vue.js">script> 7 head> 8 <body> 9 <div id="demo"> 10 <h1>我是父组件h1> 11 <my-component> 12 <p>这是初始内容1p> 13 <p>这是初始内容2p> 14 my-component> 15 div> 16 17 <template id="myComponent"> 18 <div> 19 <h1>我是子组件的标题h1> 20 <slot>没有分发内容的时候才会显示slot> 21 div> 22 template> 23 24 <script type="text/javascript"> 25 26 Vue.component('my-component',{ 27 template:'#myComponent' 28 }); 29 30 var vm = new Vue({ 31 el:'#demo' 32 }); 33 script> 34 35 body> 36 html>
结果:
除非子组件模板包含至少一个
插口,否则父组件的内容将会被丢弃(其他情况2)。当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身。
最初在
标签中的任何内容都被视为备用内容。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。
- 其他情况1:删除父组件模板中的内容
- 其他情况2:删除子组件模板里的
- 其他情况3:子组件里有多个
(当然,这里有两个匿名name
来配置如何分发内容。详见第三节 具名slot)
3、具名slot
元素可以用一个特殊的属性 name
来配置如何分发内容。多个 slot 可以有不同的名字。具名 slot 将匹配内容片段中有对应 slot
特性的元素。
仍然可以有一个匿名 slot,它是默认 slot,作为找不到匹配的内容片段的备用插槽。如果没有默认的 slot,这些找不到匹配的内容片段将被抛弃。
demo :
1 DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>具名slottitle> 6 <script type="text/javascript" src="../lib/js/vue.js">script> 7 head> 8 <body> 9 <div id="demo"> 10 <h1>我是父组件h1> 11 <my-component> 12 <h1 slot="header">这里可能是一个页面标题h1> 13 <p>主要内容的一个段落。p> 14 <p>另一个主要段落。p> 15 <p slot="footer">这里有一些联系信息p> 16 my-component> 17 div> 18 19 <template id="myComponent"> 20 <div class="container"> 21 <header> 22 <slot name="header">slot> 23 header> 24 <main> 25 <slot>这里是匿名slotslot> 26 main> 27 <footer> 28 <slot name="footer">slot> 29 footer> 30 div> 31 template> 32 33 <script type="text/javascript"> 34 35 Vue.component('my-component',{ 36 template:'#myComponent' 37 }); 38 39 var vm = new Vue({ 40 el:'#demo' 41 }); 42 script> 43 body> 44 html>
4、作用域插槽
2.1.0新增
作用域插槽是一种特殊类型的插槽,用作使用一个 (能够传递数据到) 可重用模板替换已渲染元素。
1 <div class="parent"> 2 <child> 3 <template scope="props"> 4 <span>hello from parentspan> 5 <span>{{ props.text }}span> 6 template> 7 child> 8 div> 9 10 <template id="myComponent"> 11 <div class="child"> 12 <slot text="hello from child">没有分发内容的时候才会显示slot> 13 div> 14 template> 15 16 <script type="text/javascript"> 17 Vue.component('child',{ 18 template:'#myComponent' 19 }); 20 21 var vm = new Vue({ 22 el:'.parent' 23 }); 24 script>
在子组件中,只需将数据传递到插槽,就像你将 props 传递给组件一样:
在父级中,具有特殊属性
scope
的元素必须存在,表示它是作用域插槽的模板。
scope
的值对应一个临时变量名,此变量接收从子组件中传递的 props 对象:
作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项。作用域插槽也可以是具名的。(线上demo)
1 DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>作用域插槽-列表title> 6 <script type="text/javascript" src="../lib/js/vue.js">script> 7 head> 8 <body> 9 <div class="parent"> 10 <my-list :items="items"> 11 <template slot="item" scope="props"> 12 <li class="my-fancy-item">{{ props.text }}li> 13 template> 14 my-list> 15 div> 16 17 <template id="myComponent"> 18 <ul> 19 <slot name="item" v-for="item in items" :text="item.text">slot> 20 ul> 21 template> 22 23 <script type="text/javascript"> 24 Vue.component('my-list',{ 25 template:'#myComponent', 26 data:function () { 27 return { 28 items:[ 29 {id:1,text:'列表1'}, 30 {id:2,text:'列表2'}, 31 {id:3,text:'列表3'}, 32 {id:4,text:'列表4'} 33 ] 34 } 35 } 36 }); 37 38 var vm = new Vue({ 39 el:'.parent', 40 data:{ 41 items:[] 42 } 43 }); 44 script> 45 46 body> 47 html>
三、动态组件
通过使用保留的
元素,动态地绑定到它的 is
特性,我们让多个组件可以使用同一个挂载点,并动态切换:
var vm = new Vue({ el: '#example', data: { currentView: 'component1' //默认选中的组件 }, components: { component1: { /* ... */ }, component2: { /* ... */ }, component13: { /* ... */ } } })
也可以直接绑定到组件对象上:
var Home = { template: 'Welcome home!
' } var vm = new Vue({ el: '#example', data: { currentView: Home } })
通过具体实例来说明:demo
1 <div class="container"> 2 3 <ul class="nav nav-pills"> 4 <li><a href="javascript:void(0)" @click="toggleTab(0)">{{tabText1}}a>li> 5 <li><a href="javascript:void(0)" @click="toggleTab(1)">{{tabText2}}a>li> 6 <li><a href="javascript:void(0)" @click="toggleTab(2)">{{tabText3}}a>li> 7 ul> 8 9 <div class="content"> 10 11 <keep-alive><component :is="currentView">component>keep-alive> 12 div> 13 div> 14 15 16 <template id="tab-content1"> 17 <div>这是第一个选项卡的内容!div> 18 template> 19 20 <template id="tab-content2"> 21 <div>这是第二个选项卡的内容!div> 22 template> 23 24 <template id="tab-content3"> 25 <div>这是第三个选项卡的内容!div> 26 template> 27 28 <script type="text/javascript"> 29 //局部注册组件(选项卡内容) 30 var tab1 = { 31 template:'#tab-content1' 32 }; 33 var tab2 = { 34 template:'#tab-content2' 35 }; 36 var tab3 = { 37 template:'#tab-content3' 38 }; 39 40 var vm = new Vue({ 41 el:'.container', 42 data:{ 43 tabText1:'选项卡1', 44 tabText2:'选项卡2', 45 tabText3:'选项卡3', 46 currentView:tab1 47 }, 48 //注册局部组件 49 components:{ 50 tabComponent1:tab1, 51 tabComponent2:tab2, 52 tabComponent3:tab3 53 }, 54 methods:{ 55 toggleTab:function (i) { 56 var arr = ['tabComponent1','tabComponent2','tabComponent3']; 57 this.currentView = arr[i]; 58 } 59 } 60 61 }) 62 script>
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个
keep-alive
指令参数:<keep-alive> <component :is="currentView"> component> keep-alive>
四、子组件索引
尽管有 props 和 events,但是有时仍然需要在 JavaScript 中直接访问子组件。为此可以使用 ref
为子组件指定一个索引 ID。例如:
1 <div id="parent"> 2 <user-profile ref="profile">user-profile> 3 div> 4 5 <script type="text/javascript"> 6 Vue.component('user-profile',{ 7 template:'{{message}}
', 8 data:function () { 9 return { 10 message:'这里是子组件索引!' 11 } 12 } 13 }); 14 15 var parent = new Vue({ 16 el: '#parent' 17 }); 18 // 访问子组件 19 var child = parent.$refs.profile; 20 console.log(child.$data); //打印子组件的数据
当 ref
和 v-for
一起使用时,ref 是一个数组,包含相应的子组件。
$refs
只在组件渲染完成后才填充,并且它是非响应式的。它仅仅作为一个直接访问子组件的应急方案——应当避免在模版或计算属性中使用 $refs
。
五、递归组件
组件在它的模板内可以递归地调用自己,不过,只有当它有 name 选项时才可以
name: 'unique-name-of-my-component'
当你利用Vue.component
全局注册了一个组件, 全局的ID作为组件的 name
选项,被自动设置.
Vue.component('unique-name-of-my-component', { // ... })
如果你不谨慎, 递归组件可能导致死循环。要确保递归调用有终止条件 (比如递归调用时使用
v-if
并让他最终返回false)
实例:依次输出123456789
(1)定义一个组件模板,基本标签为0,然后调用该组件。并且将数值加1(如果加1之后不超过10)。注意这些操作一定要放在一个标签内,如下代码中的div,否则会报错。
(2)定义父组件,并且传入初始count值
(3)注册组件,并且定义v-if的成立条件
效果图:
1 2 3 4 5递归组件 6 7 8 91012 13 1411 15 {{count}} 1618 19 20 37 38if="countNum">{{count}} 17
六、组件间的循环使用
假设有两个组件称为 A 和 B,模块系统看到它需要 A,但是首先 A 需要 B,但是 B 需要 A,而 A 需要 B,陷入了一个无限循环,因此不知道到底应该先解决哪个。如下:
当使用Vue.component
将这两个组件注册为全局组件的时候,框架会自动为你解决这个矛盾。
然而,如果使用诸如Webpack或者Browserify之类的模块化管理工具来requiring/importing组件的话,就会报错了。
要解决这个问题,我们需要在其中一个组件中 (比如 A) 告诉模块化管理系统,“A 虽然需要 B,但是不需要优先导入 B”
在我们的例子中,我们选择在tree-folder
组件中来告诉模块化管理系统循环引用的组件间的处理优先级,我们知道引起矛盾的子组件是tree-folder-contents
,所以我们在beforeCreate
生命周期钩子中去注册它:
beforeCreate: function () { this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default }
七、组件综合案例-树形组件
demo完成效果:
步骤:
1、首先在创建根实例时定义一组数据,在父组件上利用v-for指令循环数据,将每一项数据通过props传递给子组件(列表项li),并将数据的name值在li显示。
HTML:
1 <div class="container"> 2 <ul class="list-group"> 3 <tree-node v-for="item in items" :parent_node="item" :key="item.name">tree-node> 4 ul> 5 div> 6 <template id="treeNode"> 7 <li class="list-group-item"> 8 <div> 9 {{parent_node.name}} 10 div> 11 li> 12 template>
JS:
1 Vue.component('tree-node',{ 2 name:'tree-node', 3 template:'#treeNode', 4 props:['parent_node'] 5 }); 6 7 var vm = new Vue({ 8 el:'.container', 9 data:{ 10 items:[ 11 { 12 name:'列表1' 13 }, 14 { 15 name:'列表2' 16 }, 17 { 18 name:'列表3' 19 } 20 ] 21 } 22 })
注意:
key
的特殊属性主要用在 Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes。如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用key,它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素。有相同父元素的子元素必须有独特的key。重复的key会造成渲染错误。最常见的用例是结合
v-for。
<ul> <li v-for="item in items" :key="item.id">...li> ul>
2、给数据加一项data,用来存放二级列表数据;循环使用组件tree-node,并且将传递给v-for的列表项改为对应二级列表的列表项。
1 var vm = new Vue({ 2 el:'.container', 3 data:{ 4 items:[ 5 { 6 name:'列表1', 7 data:[ 8 {name:'列表1-1'}, 9 {name:'列表1-2'}, 10 {name:'列表1-3'} 11 ] 12 }, 13 { 14 name:'列表2', 15 data:[ 16 {name:'列表2-1'}, 17 {name:'列表2-2'}, 18 {name:'列表2-3'} 19 ] 20 }, 21 { 22 name:'列表3', 23 data:[] 24 } 25 ] 26 } 27 })
1 <template id="treeNode"> 2 <li class="list-group-item"> 3 <div> 4 {{parent_node.name}} 5 div> 6 <ul> 7 <tree-node v-for="child_node in parent_node.data" :parent_node="child_node" :key="child_node.name">tree-node> 8 ul> 9 li> 10 template>
此时效果如下:
3、给一级列表项数据添加open字段,用来显示列表是否展开;
(我默认列表1展开,列表2和列表3不展开)
4、给一级列表添加两个图标(open和close图标),用v-if来判断哪一个图标显示。两个图标的显示条件都是列表项存在第二级列表数据-data。
1 <template id="treeNode"> 2 <li class="list-group-item"> 3 <div> 4 <span v-if="!parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-close">span> 5 <span v-if="parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-open">span> 6 {{parent_node.name}} 7 div> 8 <ul> 9 <tree-node v-for="child_node in parent_node.data" :parent_node="child_node" :key="child_node.name">tree-node> 10 ul> 11 li> 12 template>
此时效果为:
5、用v-show和v-if给二级列表加上显示条件。当没有data数据的时候,二级列表不存在;当open字段为false的时候,二级列表不显示;
1 <template id="treeNode"> 2 <li class="list-group-item"> 3 <div> 4 <span v-if="!parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-close">span> 5 <span v-if="parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-open">span> 6 {{parent_node.name}} 7 div> 8 9 <ul v-if="parent_node.data" v-show="parent_node.open"> 10 <tree-node v-for="child_node in parent_node.data" :parent_node="child_node" :key="child_node.name">tree-node> 11 ul> 12 13 li> 14 template>
此时显示效果为:
至此demo算是已经完成了,最后一步则是给一级列表加上点击事件。
6、给列表项中的div加上点击事件
本文的Demo和源代码已放到GitHub https://github.com/zwl-jasmine95/Vue_test
如果您觉得本篇内容不错,请点个赞,或在GitHub上加个星星!