vue.js学习之组件(下篇)
本文的Demo和源代码已放到GitHub,如果您觉得本篇内容不错,请点个赞,或在GitHub上加个星星!
https://github.com/zwl-jasmine95/Vue_test
以下所有知识都是基于vue.js 2.0版本
一、组件编译作用域
<child-component> {{ message }}</child-component>
message 应该绑定到父组件的数据,组件作用域简单地说是:
父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。
组件的模板是在其作用域内编译的,那么组件选项对象中的数据也应该是在组件模板中使用的。
<div id="component-demo">
<!-- #component-demo是Vue实例挂载的元素,应该在挂载元素范围内使用组件-->
<hello-component></hello-component>
</div> <script type="text/javascript">
Vue.component('hello-component',{
template:'<h1>hello component!</h1>'
});
var vm = new Vue({
el:'#component-demo'
}); </script>
在创建一个Vue实例时,除了将它挂载到某个HTML元素下,还要编译组件,将组件转换为HTML片段。
除此之外,Vue实例还会识别其所挂载的元素下的<hello-component>标签,然后将<hello-component>标签替换为HTML片段。
实际上浏览器仍然是不理解<hello-component>标签的,

组件在使用前,经过编译已经被转换为HTML片段了,组件是有一个作用域的,那么组件的作用域可以将它理解为组件模板包含的HTML片段,组件模板内容之外就不是组件的作用域了。
例如,hello-component组件的作用域只是下面这个小片段:

通俗地讲,在子组件中定义的数据,只能用在子组件的模板。在父组件中定义的数据,只能用在父组件的模板。如果父组件的数据要在子组件中使用,则需要子组件定义props。
二、使用slot分发内容
1、什么是“内容分发”?
在使用组件时,往往会这样:
<app>
<app-header></app-header>
<app-footer></app-footer>
</app>
注意两点:
<app>组件不知道它会收到什么内容。这是由使用<app>的父组件决定的。<app>组件很可能有它自己的模版。
为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为 内容分发。Vue.js 实现了一个内容分发 API,参照了当前 Web 组件规范草案,使用特殊的 <slot> 元素作为原始内容的插槽。
2、单个slot
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>单个slot</title>
<script type="text/javascript" src="../lib/js/vue.js"></script>
</head>
<body>
<div id="demo">
<h1>我是父组件</h1>
<my-component>
<p>这是初始内容1</p>
<p>这是初始内容2</p>
</my-component>
</div> <template id="myComponent">
<div>
<h1>我是子组件的标题</h1>
<slot>没有分发内容的时候才会显示</slot>
</div>
</template> <script type="text/javascript"> Vue.component('my-component',{
template:'#myComponent'
}); var vm = new Vue({
el:'#demo'
});
</script> </body>
</html>
结果:


除非子组件模板包含至少一个
<slot>插口,否则父组件的内容将会被丢弃(其他情况2)。当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身。最初在
<slot>标签中的任何内容都被视为备用内容。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。
- 其他情况1:删除父组件模板中的内容

- 其他情况2:删除子组件模板里的<slot>

- 其他情况3:子组件里有多个<slot>


(当然,这里有两个匿名<slot>会有警告,应该用特殊的属性 name 来配置如何分发内容。详见第三节 具名slot)
3、具名slot
<slot> 元素可以用一个特殊的属性 name 来配置如何分发内容。多个 slot 可以有不同的名字。具名 slot 将匹配内容片段中有对应 slot 特性的元素。
仍然可以有一个匿名 slot,它是默认 slot,作为找不到匹配的内容片段的备用插槽。如果没有默认的 slot,这些找不到匹配的内容片段将被抛弃。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>具名slot</title>
<script type="text/javascript" src="../lib/js/vue.js"></script>
</head>
<body>
<div id="demo">
<h1>我是父组件</h1>
<my-component>
<h1 slot="header">这里可能是一个页面标题</h1>
<p>主要内容的一个段落。</p>
<p>另一个主要段落。</p>
<p slot="footer">这里有一些联系信息</p>
</my-component>
</div> <template id="myComponent">
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot>这里是匿名slot</slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template> <script type="text/javascript"> Vue.component('my-component',{
template:'#myComponent'
}); var vm = new Vue({
el:'#demo'
});
</script>
</body>
</html>


4、作用域插槽
2.1.0新增
作用域插槽是一种特殊类型的插槽,用作使用一个 (能够传递数据到) 可重用模板替换已渲染元素。
<div class="parent">
<child>
<template scope="props">
<span>hello from parent</span>
<span>{{ props.text }}</span>
</template>
</child>
</div> <template id="myComponent">
<div class="child">
<slot text="hello from child">没有分发内容的时候才会显示</slot>
</div>
</template> <script type="text/javascript">
Vue.component('child',{
template:'#myComponent'
}); var vm = new Vue({
el:'.parent'
});
</script>

在子组件中,只需将数据传递到插槽,就像你将 props 传递给组件一样:
在父级中,具有特殊属性
scope的<template>元素必须存在,表示它是作用域插槽的模板。scope的值对应一个临时变量名,此变量接收从子组件中传递的 props 对象:
作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项。作用域插槽也可以是具名的。(线上demo)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>作用域插槽-列表</title>
<script type="text/javascript" src="../lib/js/vue.js"></script>
</head>
<body>
<div class="parent">
<my-list :items="items">
<template slot="item" scope="props">
<li class="my-fancy-item">{{ props.text }}</li>
</template>
</my-list>
</div> <template id="myComponent">
<ul>
<slot name="item" v-for="item in items" :text="item.text"></slot>
</ul>
</template> <script type="text/javascript">
Vue.component('my-list',{
template:'#myComponent',
data:function () {
return {
items:[
{id:1,text:'列表1'},
{id:2,text:'列表2'},
{id:3,text:'列表3'},
{id:4,text:'列表4'}
]
}
}
}); var vm = new Vue({
el:'.parent',
data:{
items:[]
}
});
</script> </body>
</html>

(这里代码中删除
和
两处对效果并没有什么影响)
三、动态组件
通过使用保留的 <component> 元素,动态地绑定到它的 is 特性,我们让多个组件可以使用同一个挂载点,并动态切换:
var vm = new Vue({
el: '#example',
data: {
currentView: 'component1' //默认选中的组件
},
components: {
component1: { /* ... */ },
component2: { /* ... */ },
component13: { /* ... */ }
}
})
<component v-bind:is="currentView">
<!-- 组件在 vm.currentview 变化时改变! -->
</component>
也可以直接绑定到组件对象上:
var Home = {
template: '<p>Welcome home!</p>'
}
var vm = new Vue({
el: '#example',
data: {
currentView: Home
}
})
通过具体实例来说明:demo

<div class="container">
<!--导航栏-->
<ul class="nav nav-pills">
<li><a href="javascript:void(0)" @click="toggleTab(0)">{{tabText1}}</a></li>
<li><a href="javascript:void(0)" @click="toggleTab(1)">{{tabText2}}</a></li>
<li><a href="javascript:void(0)" @click="toggleTab(2)">{{tabText3}}</a></li>
</ul>
<!-- 点击导航后要切换的内容容器 -->
<div class="content">
<!-- 如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数 -->
<keep-alive><component :is="currentView"></component></keep-alive>
</div>
</div> <!-- 点击导航后要切换的内容 -->
<template id="tab-content1">
<div>这是第一个选项卡的内容!</div>
</template> <template id="tab-content2">
<div>这是第二个选项卡的内容!</div>
</template> <template id="tab-content3">
<div>这是第三个选项卡的内容!</div>
</template> <script type="text/javascript">
//局部注册组件(选项卡内容)
var tab1 = {
template:'#tab-content1'
};
var tab2 = {
template:'#tab-content2'
};
var tab3 = {
template:'#tab-content3'
}; var vm = new Vue({
el:'.container',
data:{
tabText1:'选项卡1',
tabText2:'选项卡2',
tabText3:'选项卡3',
currentView:tab1
},
//注册局部组件
components:{
tabComponent1:tab1,
tabComponent2:tab2,
tabComponent3:tab3
},
methods:{
toggleTab:function (i) {
var arr = ['tabComponent1','tabComponent2','tabComponent3'];
this.currentView = arr[i];
}
} })
</script>
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个
keep-alive指令参数:<keep-alive>
<component :is="currentView">
<!-- 非活动组件将被缓存! -->
</component>
</keep-alive>
四、子组件索引
尽管有 props 和 events,但是有时仍然需要在 JavaScript 中直接访问子组件。为此可以使用 ref 为子组件指定一个索引 ID。例如:

<div id="parent">
<user-profile ref="profile"></user-profile>
</div> <script type="text/javascript">
Vue.component('user-profile',{
template:'<p>{{message}}</p>',
data:function () {
return {
message:'这里是子组件索引!'
}
}
}); var parent = new Vue({
el: '#parent'
});
// 访问子组件
var child = parent.$refs.profile;
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)
(1)定义一个组件模板,基本标签为<span>0</span>,然后调用该组件。并且将数值加1(如果加1之后不超过10)。注意这些操作一定要放在一个标签内,如下代码中的div,否则会报错。

(2)定义父组件,并且传入初始count值

(3)注册组件,并且定义v-if的成立条件

效果图:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>递归组件</title>
<script type="text/javascript" src="../lib/js/vue.js"></script>
</head>
<body>
<div id="parent">
<counter-component :count="0"></counter-component>
</div> <template id="counterComponent">
<div>
<span>{{count}}</span>
<counter-component :count="count+1" v-if="countNum">{{count}}</counter-component>
</div>
</template> <script type="text/javascript">
Vue.component('counter-component',{
name:'counter-component',
template:'#counterComponent',
props:['count'],
computed:{
countNum:function () {
return this.count < 10;
}
}
}); var parent = new Vue({
el: '#parent'
}); </script>
</body>
</html>
六、组件间的循环使用
假设有两个组件称为 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:
<div class="container">
<ul class="list-group">
<tree-node v-for="item in items" :parent_node="item" :key="item.name"></tree-node>
</ul>
</div>
<template id="treeNode">
<li class="list-group-item">
<div>
{{parent_node.name}}
</div>
</li>
</template>
JS:
Vue.component('tree-node',{
name:'tree-node',
template:'#treeNode',
props:['parent_node']
});
var vm = new Vue({
el:'.container',
data:{
items:[
{
name:'列表1'
},
{
name:'列表2'
},
{
name:'列表3'
}
]
}
})

注意:
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的列表项改为对应二级列表的列表项。
var vm = new Vue({
el:'.container',
data:{
items:[
{
name:'列表1',
data:[
{name:'列表1-1'},
{name:'列表1-2'},
{name:'列表1-3'}
]
},
{
name:'列表2',
data:[
{name:'列表2-1'},
{name:'列表2-2'},
{name:'列表2-3'}
]
},
{
name:'列表3',
data:[]
}
]
}
})
<template id="treeNode">
<li class="list-group-item">
<div>
{{parent_node.name}}
</div>
<ul>
<tree-node v-for="child_node in parent_node.data" :parent_node="child_node" :key="child_node.name"></tree-node>
</ul>
</li>
</template>
此时效果如下:

3、给一级列表项数据添加open字段,用来显示列表是否展开;

(我默认列表1展开,列表2和列表3不展开)
4、给一级列表添加两个图标(open和close图标),用v-if来判断哪一个图标显示。两个图标的显示条件都是列表项存在第二级列表数据-data。
<template id="treeNode">
<li class="list-group-item">
<div>
<span v-if="!parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-close"></span>
<span v-if="parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-open"></span>
{{parent_node.name}}
</div>
<ul>
<tree-node v-for="child_node in parent_node.data" :parent_node="child_node" :key="child_node.name"></tree-node>
</ul>
</li>
</template>
此时效果为:

5、用v-show和v-if给二级列表加上显示条件。当没有data数据的时候,二级列表不存在;当open字段为false的时候,二级列表不显示;
<template id="treeNode">
<li class="list-group-item">
<div>
<span v-if="!parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-close"></span>
<span v-if="parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-open"></span>
{{parent_node.name}}
</div> <ul v-if="parent_node.data" v-show="parent_node.open">
<tree-node v-for="child_node in parent_node.data" :parent_node="child_node" :key="child_node.name"></tree-node>
</ul> </li>
</template>
此时显示效果为:

至此demo算是已经完成了,最后一步则是给一级列表加上点击事件。
6、给列表项中的div加上点击事件

本文的Demo和源代码已放到GitHub https://github.com/zwl-jasmine95/Vue_test
如果您觉得本篇内容不错,请点个赞,或在GitHub上加个星星!
vue.js学习之组件(下篇)的更多相关文章
- Vue.js学习 Item11 – 组件与组件间的通信
什么是组件? 组件(Component)是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能.在有 ...
- vue.js学习之组件(上篇)
本文的Demo和源代码已放到GitHub,如果您觉得本篇内容不错,请点个赞,或在GitHub上加个星星! https://github.com/zwl-jasmine95/Vue_test 以下所有知 ...
- Vue.js学习-组件注册与使用
Vue.js学习文档 地址:https://cn.vuejs.org/v2/guide/ 关于自定义组件注册: 建议将<script></script>放在body标签之后 H ...
- Vue.js 学习笔记 第7章 组件详解
本篇目录: 7.1 组件与复用 7.2 使用props传递数据 7.3 组件通讯 7.4 使用slot分发内容 7.5 组件高级用法 7.6 其他 7.7 实战:两个常用组件的开发 组件(Compon ...
- Vue.js 学习笔记之六:构建更复杂的组件
在掌握了如何构建与编译 Vue 组件的基础知识之后,接下来就可以试着来构建一些更具有实际用处的复杂组件了.为了赋予组件更具实用性的后面,首先要做的就是让这些组件具备监听用户自定义事件的能力,并且允许用 ...
- 偏前端-vue.js学习之路初级(二)组件化构建
vue.js 组件化构建 组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型.自包含和通常可复用的组件构建大型应用.仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树: ...
- vue.js学习之better-scroll封装的轮播图初始化失败
vue.js学习之better-scroll封装的轮播图初始化失败 问题一:slider组件初始化失败 原因:页面异步获取数据很慢,导致slider初始化之后,数据还未获取到,导致图片还未加载 解决方 ...
- vue.js学习之 跨域请求代理与axios传参
vue.js学习之 跨域请求代理与axios传参 一:跨域请求代理 1:打开config/index.js module.exports{ dev: { } } 在这里面找到proxyTable{}, ...
- vue.js学习记录
vue.js学习记录 文章已同步我的github笔记https://github.com/ymblog/blog,欢迎大家加star~~ vue实例 生命周期 beforeCreate:不能访问thi ...
随机推荐
- tp框架---View视图层---模板继承(举例说明)
当我们做动态页面时,我们会发现一个网站的头部和尾部是相同的,那么我们如何用tp框架来做模板呢 ? 先看一下注意事项: (1)每个区块由<block></block>标签组成 ( ...
- Chrome浏览器扩展开发系列之七:override页面
Chrome浏览器通常提供了一些默认页面,如标签管理器页面chrome://bookmarks.浏览历史记录页面chrome://history或新建Tab页面chrome://newtab等. Ch ...
- s2-048远程代码执行漏洞
在Struts 2.3.x 系列的 Showcase 应用中演示Struts2整合Struts 1 的插件中存在一处任意代码执行漏洞.当你的应用使用了Struts2 Struts1的插件时,可能导致不 ...
- 使用Jenkins进行持续集成ionic3项目
Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能. 网上大多数是关于.net web网站以及 ...
- oracle默认用户名及密码
oracle默认用户名及密码(网摘) (1)user: internal password :oracle (2)user: s ...
- .NetCore~Json代替了Xml
回到目录 在进行.netCore时代后,最大的变化就是对Json的使用更加主动,基本代替了之前的XML,像一些用户配置,系统配置,包包配置等都是基于json的,而web.config这个文件基本变成一 ...
- 基于Node.js的微信JS-SDK后端接口实现
做了一个网站,放到线上,用微信打开,点击分享,可是分享后发给朋友的链接卡片是微信默认自带的,如下: 这标题,描述以及图片是默认自带的,丑不说,分享给别人还以为是盗号网站呢,而接入微信的JSSDK后,分 ...
- Android - 读取JSON文件数据
Android读取JSON文件数据 JSON - JavaScript Object Notation 是一种存储和交换文本信息的语法. JSON对象在花括号中书写.用逗号来分隔值. JSON数组在方 ...
- 发博客用的一些HTML
这个世界,在发生什么? 移动光标 <p style="background: #999999; padding: 5px; font-size: 22px;">< ...
- SpringBoot系列一(入门,ORM,Transaction,log4j2等)
今天写篇springboot的博客,主要介绍一下springboot搭建以及一些整合. 首先介绍springboot搭建,我今天选择Maven,想用Gradle搭建的就自己百度一下吧,访问" ...

