接着前面的内容:https://www.cnblogs.com/yanggb/p/12592256.html

组件基础

组件化是vue的一个重要特性,也是vue学习中非常重要的一个知识点。

基础示例

这里有一个vue组件的实例:

// 定义一个名为button-counter的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

从上面的实例可以看出,创建组件使用的是【Vue.component()】方法,组件的内容写在template属性中。组件是可复用的vue实例,且带有一个名字(在这个例子中是<button-counter>)。创建组件之后,我们就可以在一个通过new Vue创建的vue根实例中把这个组件作为自定义元素来使用。

<div id="components-demo">
<button-counter></button-counter>
</div>
new Vue({ el: '#components-demo' })

因为组件是可复用的vue实例,因此它们与new Vue接收相同的选项,比如data、computed、watch、methods和生命周期钩子函数等,仅有的例外是像el这样根实例特有的选项。

组件的复用

在组件被创建了之后,可以将组件进行任意次数的复用。

<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>

每使用一次组件,就会有一个它的新实例被创建,每个实例中独立维护自己的属性,因为每个实例都拥有自己的私有作用域,并不共享数据。

data必须是一个函数

在上面的例子中,即定义<button-counter>组件的时候,你可能会发现它的data并不是像这样直接提供一个对象:

data: {
count: 0
}

取而代之的是,一个组件的data选项必须是一个函数,因此每个实例都可以维护一份被返回对象的独立拷贝:

data: function () {
return {
count: 0
}
}

如果vue没有这条规则的话,组件实例之间就会相互影响,因为这个时候数据是共享的。而返回函数的目的就在于此,因为javascript的函数在执行的时候会创建自己的私有作用域,这样就将单个组件实例中的数据与其他组件实例中的数据隔离开来。弄不清楚的同学可以去了解一下javascript中关于闭包的相关知识,这是javascript语言设计自身的特性所决定的。

组件的组织

通常一个应用会以一棵嵌套的组件树的形式来组织:

例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其他的像导航链接、博文之类的组件。为了能够在模板中进行使用,这些组件必须要先注册以便vue能够识别。组件的注册类型可以分为两种:全局注册和局部注册。在前面的所有内容中,组件都只是通过【Vue.component()】方法进行全局注册的。

Vue.component('my-component-name', {
// ... options ...
})

全局注册的组件可以用在其被注册之后的任何新创建(通过new Vue)的vue根实例,也包括其组件树中的所有子组件的模板中。

局部注册的方式和组件注册的其他知识将在后面深入了解组件的章节中获知,这里就先了解到这里。

通过prop向子组件传递数据

在上面提到了创建一个博文组件的事情。有一个问题是,如果你不能向这个组件传递某一篇博文的标题或者内容之类的我们想要展示的数据的话,它是没有办法使用的,因为其没有数据来源,并不知道数据到底从哪里来。因此,prop属性应运而生。

prop属性是vue提供给开发者可以在组件上注册的一些自定义属性。当一个值传递给一个prop属性的时候,它就会自动变成那个组件实例的一个属性。比如,为了给博文组件传递一个标题,我们可以使用一个【props】选项将其包含在该组件可以接受的prop列表中。

Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})

一个组件默认可以拥有任意数量的prop,任何值都可以被传递给任何prop。在上述的模板中,我们就能够在组件实例中访问这个值,就像访问data中的值一样。

在一个prop被注册之后,你就可以像这样把数据作为一个自定义的属性传递到组件中。

<blog-post title="i like yanggb"></blog-post>
<blog-post title="i do like yanggb"></blog-post>
<blog-post title="i really like yanggb"></blog-post>

然而在一个典型的应用中,你可能在data里有一个博文的数组:

new Vue({
el: '#blog-post-demo',
data: {
posts: [
{ id: 1, title: 'i like yanggb' },
{ id: 2, title: 'i do like yanggb' },
{ id: 3, title: 'i really like yanggb' }
]
}
})

并想要为每篇博文渲染一个组件:

<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"></blog-post>

如上所示,你会发现我们可以使用【v-bind】指令来动态传递prop。这在你一开始不清楚要渲染的具体内容的情况下,比如从一个api获取博文列表的时候,是非常有用的。

单个根元素

当构建一个<blog-post>组件的时候,你的模板最终会包含的东西远不止一个标题,最起码会包含这篇博文的正文:

<h3>{{ title }}</h3>
<div v-html="content"></div>

但是如果你在模板中尝试这样写的话,vue会显示一个错误,并解释【every component must have a single root element】,即每个组件必须只有一个根元素。出现这种情况的话,可以将模板的内容包裹在一个父元素内,以此来修复这个问题。

<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>

监听子组件事件

在我们开发<blog-post>组件的时候,它的一些功能可能要求我们和父级的组件进行沟通。例如我们可能会引入一个辅助功能来放大博文的字号,同时让页面的其他部分保持默认的字号。

在父组件中,先通过添加一个postFontSize的数据属性来支持这个功能。

new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [/* ... */],
postFontSize: 1
}
})

然后就可以通过这个数据属性在模板中控制所有博文的字号(包裹组件)。

<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
</div>
</div>

然后我们在每篇博文正文的前面添加一个按钮来放大字号:

Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button>
Enlarge text
</button>
<div v-html="post.content"></div>
</div>
`
})

问题是这个按钮不会做任何事:

<button>
Enlarge text
</button>

因此,在点击这个按钮的时候需要我们去告诉父级组件放大所有博文的文本,而vue实例提供了一个自定义事件的系统来解决这个问题。

父级组件可以像处理native dom事件一样通过【v-on】指令监听子组件实例的任意事件:

<blog-post
...
v-on:enlarge-text="postFontSize += 0.1"></blog-post>

同时子组件可以通过调用内建的【$emit】方法并传入事件名称来触发一个事件:

<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button

有了这个监听器,父级组件就会接收该事件并更新postFontSize的值。

官方文档给出的这个例子并不是十分清晰,讲解也并不是十分到位,理解起来可能会有一些困难。其实简单点的理解就是,父级组件在页面上定义了一个方法,并将这个方法通过【v-on:方法名】指令传递到子组件中,然后子组件通过vue提供的全局属性【$emit】去获取到父级组件传递的这个方法并通过【v-on】指令绑定到点击事件中。后面会详细学到【$emit】的相关知识,了解了之后就明白了。

使用事件抛出一个值(传递参数)

有的时候用一个事件去抛出一个特定的值(传递参数)是非常有用的,例如我们可能像让<blog-post>组件决定它的文本要放大多少,这个时候就可以使用【$emit】的第二个参数来提供这个值:

<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>

然后当在父组件监听这个事件的时候,我们就可以通过【$event】来访问到被抛出的这个值:

<blog-post
...
v-on:enlarge-text="postFontSize += $event"></blog-post>

或者,如果这个事件处理函数是一个方法:

<blog-post
...
v-on:enlarge-text="onEnlargeText"></blog-post>

那么这个值就会被作为第一参数传入到这个方法中:

methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}

这里的【$event】也是vue提供的一个全局属性,本身的含义是事件本身,默认是触发事件的处理函数的第一参数。而在上面的例子中,子组件通过【$emit】传递参数的时候,覆盖了这个第一参数,因此$event变量的值变成了传递的值,属于一种特殊的用法。你可以尝试着在子组件中执行函数而不传递参数看看,你会发现这个时候【$event】的值是事件本身。

在组件上使用【v-model】指令

自定义事件也可以用于创建支持【v-model】指令的自定义输入组件。

首先需要知道的是,【v-model】指令用在表单元素上的时候实际上相当于【v-bind:value】指令和【v-on:input】指令的组合使用。

<input v-model="searchText">
<!-- 等价于 -->
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value">

而当用在组件上的时候,【v-model】则是相当于:

<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"></custom-input>

因此,为了能让【v-model】指令在自定义组件上正常工作,这个组件内的<input>必须:

1.将其value属性绑定到一个名叫value的prop上。

2.在其input事件被触发的时候,将新的值通过自定义的input事件抛出。

Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)">
`
})

这样【v-model】指令就应该能在这个自定义组件上完美地工作起来了:

<custom-input v-model="searchText"></custom-input>

秘诀在于使用【$emit】抛出参数值。

通过插槽分发内容

和html元素一样,我们经常会需要向一个组件传递内容,像这样:

<alert-box>
Something bad happened.
</alert-box>

因此,vue提供了一个自定义的<slot>元素来实现这一个功能。

Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})

插槽相当于一种占位符的机制,在使用子组件的时候父组件将需要的html内容插入到子组件的元素中,子组件就会自动替换占位符。插槽的内容十分重要,在后面会详细学习。

动态组件

有的时候,在不同的组件之间进行动态切换是非常有用的,比如在一个多标签的界面里,可以通过vue的<component>元素并添加一个特殊的【is】属性来达到切换不同的组件显示隐藏的目的。

<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>

在这个例子中,currentTabComponent属性可以包括已注册组件的名字或一个组件的选项对象。

要注意的是,这个属性可以用于常规的html元素,但是这些元素将会被视为组件,这就意味着所有的属性都会作为dom属性被绑定。对于像value这样的对象属性,如果你想要让其如预期般工作的话,需要使用【.prop】修饰符。

解析dom模板时候的注意事项

有些html元素,比如<ul>、<ol>、<table>和<select>等,对于哪些元素可以出现在其内部是严格限制的。而有些元素,诸如<li>、<tr>和<option>,也只能出现在其他特定的元素内部。这就会导致我们在使用这些有约束条件的元素的时候遇到一些问题。

<table>
<blog-post-row></blog-post-row>
</table>

在上面的这个例子中,自定义组件<blog-pos-row>就会被视作无效的内容被提升到外部,并导致最终渲染结果出错。为了解决这样的问题,vue提供了一个特殊的【is】属性作为一个变通的方法。

<table>
<tr is="blog-post-row"></tr>
</table>

属性值是组件名,这样相当于给组件套了一层外衣<tr>,也就避免了渲染出错的问题。

另外要注意的是,当我们从以下的来源使用模板的时候,这一条限制是不存在的:

1.字符串(例如template:'...')。

2.单文件组件(.vue文件)。

3.<script type="text/x-template">。

至此,vue的相关基础知识已经学得有七七八八,接下来就该学习一些比较深入的知识了。

"我还是很喜欢你,像雨落湖心泛涟漪,廖无声息。"

vue2.x学习笔记(十二)的更多相关文章

  1. python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL

    python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL实战例子:使用pyspider匹配输出带.html结尾的URL:@config(a ...

  2. Go语言学习笔记十二: 范围(Range)

    Go语言学习笔记十二: 范围(Range) rang这个关键字主要用来遍历数组,切片,通道或Map.在数组和切片中返回索引值,在Map中返回key. 这个特别像python的方式.不过写法上比较怪异使 ...

  3. java jvm学习笔记十二(访问控制器的栈校验机制)

    欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 本节源码:http://download.csdn.net/detail/yfqnihao/4863854 这一节,我们 ...

  4. (C/C++学习笔记) 十二. 指针

    十二. 指针 ● 基本概念 位系统下为4字节(8位十六进制数),在64位系统下为8字节(16位十六进制数) 进制表示的, 内存地址不占用内存空间 指针本身是一种数据类型, 它可以指向int, char ...

  5. Python学习笔记(十二)—Python3中pip包管理工具的安装【转】

    本文转载自:https://blog.csdn.net/sinat_14849739/article/details/79101529 版权声明:本文为博主原创文章,未经博主允许不得转载. https ...

  6. vue2.x学习笔记(二十二)

    接着前面的内容:https://www.cnblogs.com/yanggb/p/12633051.html. 自定义指令 简介 除了核心功能默认内置的指令([v-mode]和[v-show]等),v ...

  7. vue2.x学习笔记(二十六)

    接着前面的内容:https://www.cnblogs.com/yanggb/p/12682137.html. 单文件组件 介绍 在很多的vue项目中,我们都是使用[Vue.component]来定义 ...

  8. vue2.x学习笔记(二十五)

    接着前面的内容:https://www.cnblogs.com/yanggb/p/12677019.html. 过滤器 vue允许开发者自定义过滤器,可被用于一些常见的文本格式化.过滤器可以用在两个地 ...

  9. vue2.x学习笔记(二十)

    接着前面的内容:https://www.cnblogs.com/yanggb/p/12631279.html. 循环引用 递归组件 组件是可以在它们自己的模板中调用自身的,不过它们只能通过[name] ...

  10. vue2.x学习笔记(二十九)

    接着前面的内容:https://www.cnblogs.com/yanggb/p/12682822.html. 路由 官方路由 对于大多数的单页面应用,都推荐使用官方支持的vue-router库. 从 ...

随机推荐

  1. Selenium系列(十五) - Web UI 自动化基础实战(2)

    如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...

  2. SpringBoot环境搭建及第一个程序运行(详细!)

    spring boot简介 spring boot框架抛弃了繁琐的xml配置过程,采用大量的默认配置简化我们的开发过程. 所以采用Spring boot可以非常容易和快速地创建基于Spring 框架的 ...

  3. [Asp.Net Core] 关于 Blazor Server Side 的一些杂项, 感想

    在2016年, 本人就开始了一个内部项目, 其特点就是用C#构建DOM树, 然后把DOM同步到浏览器中显示. 并且在一些小工程中使用. 3年下来, 效果很不错, 但因为是使用C#来构建控件树, 在没有 ...

  4. java程序:转化金额

    在处理财务账款时,需要将转账金额写成大写的.也就是说,如果要转账123456.00元,则需要写成“壹拾贰万叁仟肆佰伍拾陆元整”.所以常常需要通过程序控制自动进行转换.本实例实现了小写金额到大写金额的转 ...

  5. html前端之css基础

    CSS 属性导航: CSS 属性组 动画 背景 边框和轮廓 框 颜色 内容页的媒体属性 尺寸 盒子模型(新) 盒子模型(旧) 字体 内容生成 网格 超链接 线框 列表 外边距 字幕 多列 内边距 页面 ...

  6. python基础知识 目录 简介

    1.1编程语言介绍与分类 什么是编程语言? 本质:与人类语言一样.沟通 电流+一堆硬件 高电压1 低电压0 高电压1 低电压0 高电压1 低电压0 8 晶体管 010101010101 play so ...

  7. Apache本地服务器搭建(Mac版)

    由于Mac自带apache服务器,所以无需下载,apache默认处于开启状态. 可以在浏览器输入localhost,显示It works!,代表目前处于开启状态,默认文件目录为/Library/Web ...

  8. 使用Putty + Xming 远程登录Linux显示图形化界面

    一般我们远程登录linux 服务器,都是使用非加密的 telnet 或者加密的 ssh.这些登录方式有一个特点:只能登录字符界面,不能运行那些带有GUI界面的程序. 有时候为了学习或者工作的需要,我们 ...

  9. 微信网页授权,获取微信code,获取access_tocken,获取用户信息

    微信开发中,经常有这样的需求:获得用户头像.绑定微信号给用户发信息.. 那么实现这些的前提就是授权!   1.配置安全回调域名: 在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 ...

  10. 如何在 Array.forEach 中正确使用 Async

    本文译自How to use async functions with Array.forEach in Javascript - Tamás Sallai. 0. 如何异步遍历元素 在第一篇文章中, ...