首发日期:2019-01-26
上节内容回顾
- 数据绑定:v-model
- 样式绑定:v-bind:class,v-bind:style
- 事件:v-on
- Vue指令
- 数组操作(知道哪些数组操作是能被vm层监听到并能响应式更新到视图上的)
- Vue的元素复用问题(不使用key时会尽量复用)
组件
【官方的话】组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:
小菜鸟的话:定义组件就好像定义了一堆“带名字”的模板,比如说可能会有叫做“顶部菜单栏”的组件,我们可以多次复用这个“顶部菜单栏”而省去了大量重复的代码。
什么是组件
- 在以前的多页面开发的时候,我们可能会经常需要一个“顶部菜单栏”,于是我们在每个html文件中都要加上关于“顶部菜单栏”的代码。可能你会想这份代码能够“复用”就好了。而组件可以定义模板,从而达到复用代码的作用。
- 组件可以大大提高我们构建页面的效率。
-
你可以将组件进行任意次数的复用
下面用代码来演示一下"复用效果":
<body>
<div id="app">
<my-template></my-template><!-- 利用组件名来使用定义的“模板” -->
<my-template></my-template>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-template',{ // 第一个参数是组件名,第二个参数是模板的内容
template: '<div><span>我的模板</span></div>'
})
var vm = new Vue({
el: '#app'
})
</script>
代码效果:
组件注册
组件注册就是“定义模板”,只有注册了的组件,Vue才能够了解怎么渲染。
全局注册组件
- 全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 实例中,也包括其组件树中的所有子组件的模板中。【一个Vue应用只有一个根实例,但还允许有其他的实例。在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。】
- 全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生
- 全局注册的组件可以在另一个组件中使用。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<ol>
<!-- 使用组件 -->
<todo-item></todo-item>
<todo-item></todo-item>
<todo-item></todo-item>
</ol>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//全局定义组件,第一个参数是组件名,template的值是组件的内容
Vue.component('todo-item', {
template: '<li>这是个待办项</li>'
})
// 实例化是必须的,要把使用组件的区域交给vue管理
var app = new Vue({
el: '#app',
})
</script>
</html>
局部注册组件
全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:
<body>
<div id="app">
<my-component></my-component><!-- 利用组件名来使用组件 -->
<my-component></my-component>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var ComponentA = { // 定义一个组件
template: '<div><span>我的模板</span></div>'
}
var vm = new Vue({
el: '#app',
components: { // 然后在实例中声明要使用这个组件,key是在这个实例中的组件名,value是组件
'my-component': ComponentA
}
})
</script>
上面的全局注册说了允许在组件中使用其他组件,但注意局部注册的组件要声明使用其他组件才能够嵌套其他组件。例如,如果你希望 ComponentA 在 ComponentB 中可用,则你需要这样写:
<body>
<div id="app">
<my-component></my-component>
<my-component-b></my-component-b>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var ComponentA = { // 1.定义一个组件
template: '<div><span>我的模板</span></div>'
}
var ComponentB = { // 2.定义一个组件
components: { //3.声明使用A组件
'my-component': ComponentA
},
template: '<my-component></my-component>' // 4.需要在组件的template中写上另一个组件
}
var vm = new Vue({
el: '#app',
components: {
'my-component': ComponentA,
'my-component-b': ComponentB
}
})
</script>
使用细节
组件注册的命名规范:
组件名可以使用类my-component-name(kebab-case (短横线分隔命名))或MyComponentName的格式(PascalCase 首字母大写命名法),使用组件的时候可以<my-component-name>
或<MyComponentName>
,但在有些时候首字母大写命名法定义组件的是不行的,所以通常推荐使用<my-component-name>
【当你使用首字母大写命名法来定义组件的时候,不能直接在body中直接写组件名,而要求写在template中,如下例】。
<body>
<div id="app">
<my-component></my-component>
<my-component-demo></my-component-demo>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component',{
template: '<div><span>我的模板A</span></div>'
})
Vue.component('my-component-demo',{ // 这个是用来测试第二种命名法定义组件的,首字母大写时要写在字符串模板中才能显示(不然显示不了)
template: '<MyComponentB></MyComponentB>'
})
Vue.component('MyComponentB',{
template: '<div><span>我的模板B</span></div>'
})
var vm = new Vue({
el: '#app'
})
</script>
组件中只有一个根元素
每个组件必须只有一个根元素!!
所以下面是不合法的:
如果你确实要有多个元素,那么要有一个根元素包裹它们:
组件也是一个实例
组件也是一个实例,所以组件也可以定义我们之前在根实例中定义的内容:data,methods,created,components等等。
但一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝
组件在某些元素中渲染出错
在一些html元素中只允许某些元素的存在,例如tbody元素中要求有tr,而不可以有其他的元素(有的话会被提到外面)。下面是一个元素被提到外面的例子【而ul并没有太严格,所以我们在前面的todo-list的例子中能够演示成功】
下图可以看的出来div被提到table外面了:
这是为什么呢?目前来说,我们在页面中其实是先经过html渲染再经过vue渲染的(后面项目话后是整体渲染成功再展示的),当html渲染时,它就发现了tr里面有一个“非法元素”,所以它就把我们自定义的组件提到了table外面。
解决方案:
使用tr元素,元素里面有属性is,is的值是我们要使用的组件名
<body>
<div id="app">
<table>
<tr is='mtr'></tr>
</table>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('mtr', {
template: `<tr><td>123</td></tr>` // 这个成功的检测要使用检测元素来看
});
var app = new Vue({
el: '#app'
})
</script>
但不会在一下情况中出错:
1. 定义组件的时候,template中包含自定义的组件
2. [单文件组件,也就是说引用vue文件来注册一个组件的时候(这个东西会在后面讲)。](https://cn.vuejs.org/v2/guide/single-file-components.html)
3. [``](https://cn.vuejs.org/v2/guide/components-edge-cases.html#X-Templates)
组件间的数据传递
在上面定义的组件中使用的数据都是固定的数据,通常我们都希望模板能根据我们传入的数据来显示。
父子组件传递数据
(子组件的意思是当前组件的直接子组件,在目前的单个html文件为例时,你可以仅认为是当前页面的非嵌套组件。后面讲到多个组件的合作时由于多个组件之间的嵌套,就形成了组件的父子、祖孙、兄弟关系)
要给子组件传递数据主要有两个步骤
- 在定义组件时,使用props:['数据名'] (可以有多个数据名) 来声明来传入的数据的名字
- 使用组件时,给组件中名为"数据名"的属性赋值
演示代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<ol>
<!-- 2.给数据赋值 -->
<todo-item todo="第一个值"></todo-item>
<todo-item todo="第二个值"></todo-item>
<todo-item :todo="msg"></todo-item>
<!-- 传入的数据可以是父组件实例中定义的数据。注意要用:来绑定,不然不会识别成实例数据,而是一个字符串 -->
</ol>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('todo-item', {
props: ['todo'], // 1.使用props来定义这个组件支持传的参数
template: '<li>{{ todo }}</li>' // 3.这里演示一下在组件中使用这个传入的参数
})
var app = new Vue({
el: '#app',
data: {
msg: 'hello world'
}
})
</script>
</html>
代码效果:很明显的,我们的值成功传给子组件了。
子组件向父组件传输数据
- 我们可以在子组件中使用emit来触发事件,然后在使用这个组件的时候绑定这个事件就可以监听到这个事件的发生(这时候调用的函数是父组件的处理函数),从而使得父组件接受到子组件传递的消息了。
要给父组件传递数据主要有两个步骤
- 在定义组件时,定义一个包含触发事件的元素,这个事件触发时将会调用emit来触发事件【例如可以在按钮上定义一个onclick事件,这个onclick事件触发时将会调用emit】
-
$emit()
可以有多个参数,第一个参数是触发的事件的名称,后面的参数都是随着这个事件向外抛出的参数。 - 使用组件时,对组件进行事件监听,监听的事件与组件内抛出的事件一致
- 定义处理目标事件的函数,函数的参数是随事件向外抛出的多个参数。
演示代码如下:
<body>
<div id="app" >
<my-component-c v-on:change-color="doSomething"></my-component-c>
<!-- 2.使用组件的时候监听一下事件,这里将会调用父组件的函数来处理事件 -->
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component-c', {
template: `
<button @click="$emit('change-color','hello')">改变颜色</button>
` // 1.定义组件,在组件内定义一个能触发click事件的按钮,onclick事件发生时将会调用emit来向外抛出事件【这里触发的事件不能使用驼峰命名法。推荐使用短横线命名法】
// 【第一个参数是自定义事件名称,第二个和后续的参数可以用于向组件抛出值,这些值会作为事件响应函数的参数】
})
var app = new Vue({
el: '#app',
data: {
msg: 'hello world'
},
methods: {
// 3.将会调用下面的函数来处理事件
doSomething: function(value){ // 这里的value是从子组件中抛出的。
console.log(value) // 打印一下
}
}
})
</script>
【小tips:上面有多重字符串的使用,普通的双引号和单引号已经不足以嵌套使用了,在外层可以使用反引号
`【esc下面那个键】来包裹,它也可以达到字符串包裹的效果,特别的是它支持多行字符串。】
非父子组件之间的传值
祖孙组件传数据、兄弟组件传数据都属于非父子组件之间的传值。
- 【如果是祖孙组件传数据,可以使用父组件传给子组件,子组件传给孙组件。但这是一个费事的做法。】
- 一般都会使用vuex,vuex就像一个变量存储器,我们可以把一些多个组件都需要用到数据存储到vuex的store中。【这个由于内容较重要,留到后面再讲】
- 只有少量组件使用某个数据的时候也可以使用bus模式,bus相当于给每一个组件都加上“同一个”新的vue实例,由于bus是实例之间共享的,当数据发生改变时,可以利用这个vue实例来调用emit方法来抛出新值,而其他组件监听bus中的事件就可以获取到新的值,这样就实现了非父子组件之间的传值。
使用bus传输数据的步骤:
- 在Vue的原型上定义vue:
Vue.prototype.bus = new Vue()
- 当数据发生变化时,调用emit:
this.bus.$emit('change',当前组件的数据)
- 在组件上监听bus的事件:
this.bus.$on('change',一个用于赋值的函数)
- 在函数中获取事件触发带过来的参数,赋给当前组件,从而实现两边数据同步。
下面的代码是点击某个组件发生数据变化时,另一个组件的数据也发生变化:
<body>
<div id="app">
<child-a></child-a>
<child-a></child-a>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 1. 给Vue的原型添加一个bus,bus是一个Vue实例
Vue.prototype.bus = new Vue()
Vue.component('child-a', {
data: function() {
return {
content: 0
}
},
methods: {
// 2.定义一个函数,能够在数据发生变化时调用emit(这里定义一个点击事件的响应函数)
handleClick: function() {
this.content+=1
this.bus.$emit('change',this.content)
}
},
mounted: function(){
var this_= this // 这里要有一个这样的赋值,下面不能直接使用this,因为在函数中this指向的已经不是当前对象了,而用_this保存的才是当前对象
// 3. 在组件中对bus中触发的事件进行监听。(当emit触发事件时会调用)
this.bus.$on('change',function(msg){ // 4.函数中的参数是emit触发事件带的参数。
this_.content = msg // 5.修改当前组件中的数据
})
},
template: `<div @click='handleClick'>{{ content }}</div>`
});
var app = new Vue({
el: '#app'
})
</script>
单向数据流
- 单向数据流:props使得父组件的数据能够传输到子组件,而且传输的源数据发生改变时,子组件也会发生改变。但如果在子组件中更改prop,这是不行的,会报警告。
- 每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。如果允许你在子组件中修改父组件传入的数据的话,使用了父组件的这个数据的所有子组件的数据都会被修改(这样就降低了组件的复用效果,导致数据流行不确定,难以确定这个数据是在哪个组件中修改的,而且是一个相对危险的行为)
- 你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。【这就是单向数据流】
- 如果你确实需要修改:
- 那么你应该创建一个子组件内部的数据,这个内部数据由传入的prop的数据进行初始化。(也就是进行数据拷贝)
- 又或者你可以使用计算属性。
Props属性
## 命名规范:
大小写问题
【有个建议,建议写属性名的时候都使用kebab-case (短横线分隔命名) 命名,因为这个的兼容效果最好】
HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。如果你在props中使用了驼峰命名法,那你在定义属性的时候需要使用kebab-case (短横线分隔命名) 命名才能正确传输数据【因为短横线后面的字符可以识别成大写,从而能够匹配到】。
如果在属性中也使用驼峰命名法命名属性的时候会报这样的错:Prop "mycontent" is passed to component , but the declared prop name is "myContent". Note that HTML attributes are case-insensitive and camelCased props need to use their kebab-case equivalents when using in-DOM templates. You should probably use "my-content" instead of "myContent"
<body>
<div id="app">
<!--下面这里会报错 -->
<child myContent='hello-1'></child>
<!-- 下面会正常 -->
<child my-content='hello-2'></child>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child', {
props:['myContent'],
template: `<div>{{myContent}}</div>`
});
var app = new Vue({
el: '#app'
})
</script>
同样的,如果在组件的template属性中使用驼峰命名法的属性,那么这个限制就不存在了。
参数校验
限制props的类型
有时候需要使用第三方的组件的时候,所以会需要传输数据给这个组件来渲染。但如何限制传入的数据的类型呢?
- 可用于限制数据类型的类型:
- String:字符串
- Number:数字
- Boolean:布尔值
- Array:数组
- Object:对象
- Date:日期
- Function
- Symbol
格式:
props: {
// 数据名:数据类型
title: String,
likes: Number,
...
}
如果传入的类型不对,那么会报Invalid prop: type check failed for prop "xxx". Expected String with value "xxx", got Number with value xxx.
的错误。
如果允许多种值,可以定义一个数组:
props: {
content: [String,Number]
}
设置默认值
我们也可以给props中的数据设置默认值,如果使用default设置值,那么没有传某个数据时默认使用这个数据。
props: {
content: {
type:[String,Number],
default:'我的默认值'
}
}
如果使用default给数组或对象类型的数据赋默认值,那么要定义成一个函数。
要求数据必传
如果要求某个数据必须传给子组件,那么可以为它设置required。
格式:
props: {
content: {
type: String,
required: true
}
}
如果没传,会报Missing required prop: "xxx"
的错。
自定义验证函数:
如果想要更精确的校验,可以使用validator,里面是一个函数,函数的第一个参数就是传入的值,当函数内返回true时,这个值才会校验通过。
以下的代码是要求传入的字符串长度大于6位的校验:
Vue.component('child', {
props: {
content: {
type: String,
validator: function(value) {
return (value.length > 6)
}
}
}
如果验证不通过,会报Invalid prop: custom validator check failed for prop "xxx"
的错。
传递静态或动态Prop
- 传递静态或动态的prop意思就是传递的是一个常量还是变量。
- 传递常量的时候:
- 如果是字符串,可以不使用v-bind。
- 如果是数字,布尔值,数组,对象,那么需要使用vue的v-bind来设置属性,因为使用普通的属性设置会被认为是字符串,使用v-bind的时候会被认为是js表达式(从而成功识别成正确的类型)。
- 传递变量的时候都要使用v-bind,不然无法识别成一个变量。
补充:
- 没有讲的内容:非 Prop 的特性
给组件绑定原生的事件
用下面的代码来说一个问题:
<html lang="en">
<head>
<meta charset="utf-8">
<title>demo</title>
</head>
<body>
<div id="app">
<child @click='myFunction'></child>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child',{
template:`
<button>按钮</button>
`
});
var vm = new Vue({
el: '#app',
methods: {
myFunction: function() {
console.log('haha')
}
}
})
</script>
</html>
上面的代码你会发现点击了按钮却没有调用函数。
而下面的按钮按了会打出child。
<html lang="en">
<head>
<meta charset="utf-8">
<title>demo</title>
</head>
<body>
<div id="app">
<child @click='myFunction'></child>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child',{
template:`
<button @click='childFunction'>按钮</button>
`,
methods: {
childFunction: function() {
console.log('child')
}
}
});
var vm = new Vue({
el: '#app',
methods: {
myFunction: function() {
console.log('haha')
}
}
})
</script>
</html>
- 在上面我们提过了父子数据传递,我们知道了父组件的数据需要props来传递给子组件,说明了父组件的数据是不跟子组件共享的。
- 而事件也是这样的,在我们使用组件的时候,并不能直接监听一些事件的发生。【例如上面的子组件传数据给父组件也需要使用emit。】
- 对于父组件来说,这个组件是子组件下的按钮,所以直接点击这个按钮并不会触发事件【或者说你可以认为点击了事件了,但是没有emit出来,所以父组件监听不到】。
- 而如果希望点击按钮的时候能够触发出按钮的原生事件(不把它当作子组件下的按钮),那么需要把它绑定成原生事件。我们可以使用.native来修饰事件来说明监听的是一个原生事件。
下面的代码是使用了emit来达到同样效果的代码:
<html lang="en">
<head>
<meta charset="utf-8">
<title>demo</title>
</head>
<body>
<div id="app">
<child @click='myFunction'></child>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child',{
template:`
<button @click="$emit('click')">按钮</button>
`
});
var vm = new Vue({
el: '#app',
methods: {
myFunction: function() {
console.log('haha')
}
}
})
</script>
</html>
template
- template是vue中的一个元素。它是一个不会渲染到页面的元素,通常地它有如下几个用处:
- 由于不会被渲染处理,可以在template上使用使用v-if,把一个
<template>
元素当做不可见的包裹元素,让成组的元素能够统一被渲染出来。 - 也可以使用v-for,也是把
<template>
元素当做不可见的包裹元素 - 其他特殊的用途:例如下面要说插槽的slot-scope会用到。
- 由于不会被渲染处理,可以在template上使用使用v-if,把一个
在template上使用v-if
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
使用v-for
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
插槽
- 我们可以用prop来传递数据给子组件,而插槽是用来分发内容的。
- 我们有时候希望在使用子组件的时候顺便传给它一些内容,那么可以使用插槽。
- 【props适合于传输数据,而slot通常用来达到“插入内容”的效果;使用props的话需要子组件声明使用,而slot相较随意】
通过插槽分发内容
具名插槽
插槽也可以分发多份数据。使用指定的插槽名就可以获取指定的插槽数据。
插槽的默认内容
如果没有数据传过来,那么插槽可以定义一个默认的数据用来显示。
作用域插槽
- 有时候我们希望获取组件中的slot的值来进行其他操作。比如,可能需要根据一个值来显示不同的内容。这时候我们可以使用slot-scope来获取组件中传过来的slot的值。
- slot-scope的值是可以随意的,它代表作用于插槽的名字。
- 这时候不再是插入数据给插槽,而是从插槽中获取数据了。
<body>
<div id="app">
<child-a>
<template slot-scope='props'> <!--2. slot-scope的属性值是可以随意的,代表作用域插槽的名字 -->
<h3>{{props.index}}</h3> <!--3. props.xxx 是传过来的值的名字,值需要绑定到slot中 -->
</template>
</child-a>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.prototype.bus = new Vue()
Vue.component('child-a', {
data: function() {
return {
list: [2,4,6,8,10]
}
},
// 1. 绑定属性,名为index,那么作用域插槽就可以获取名为index的数据
template: `<div>
<slot v-for='item of list' :index=item></slot>
</div>`
});
var app = new Vue({
el: '#app'
})
</script>
动态组件
is
- 动态组件是一个有趣的东西,它相当于一张空白的组件,只要你告诉它它是什么组件,它就变成了什么组件。
- 有时候我们希望发生了xxx事件之后,页面的某个组件变成另一个组件。【你可能会产生如下图的需求:点击按钮,切换登录方式】
- 我们只需要给component元素设置上is属性就可以把component元素渲染成目标组件了。
<component :is='组件名'></component>
如果改变了is属性的值,那么渲染的组件就会发生变化。 - is可以是v-bind的,也可以是非v-bind的,如果希望它是动态可变化的,那么应该使用v-bind:is,这样才能传入一个变量,从而动态渲染组件。【非v-bind的时候传入的值会被认为一个字符串,然后直接把这个字符串认为是组件名】
下面是上图的演示代码:
<body>
<div id="app">
<button @click='changeLogin'>切换登录方式</button>
<component :is='loginType'></component>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('userInfoLogin',{
template:`
<div>
<div>用户名登陆:<input type="text"></div>
<div>请输入密码:<input type="password"></div>
</div>
`
});
Vue.component('phoneLogin',{
template:`
<div>
<div>请输入手机号:<input type="text"></div>
<div>请输入验证码:<input type="text"></div>
</div>
`
})
var vm = new Vue({
el: '#app',
data: {
loginType:'userInfoLogin'
},
methods: {
changeLogin: function() {
this.loginType = this.loginType === 'userInfoLogin'?'phoneLogin':'userInfoLogin';
}
}
})
</script>
keep-alive
- 被keep-alive元素包裹的组件会被缓存,缓存之后组件重新渲染时会直接从缓存中获取,避免了每次组件都重新渲染。
下面以上面的动态组件切换为例:
如果给上面的登录组件都加上一个created用来显示渲染次数的话,我们就可以看到是不是每次切换都会重新渲染。
如果加了keep-alive之后不再重复输出,那么就说明组件被缓存了。
<body>
<div id="app">
<button @click='changeLogin'>切换登录方式</button><!-- 2.多次点击切换,查看组件渲染情况 -->
<keep-alive>
<component :is='loginType'></component>
<!-- 4.注意在keep-alive包裹之前和之后的控制台输出情况 -->
</keep-alive>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('userInfoLogin',{
template:`
<div>
<div>用户名登陆:<input type="text"></div>
<div>请输入密码:<input type="password"></div>
</div>
`,
created: function(){
console.log('userInfoLogin')// 1.用来说明渲染了userInfoLogin组件,多次渲染则多次输出
}
});
Vue.component('phoneLogin',{
template:`
<div>
<div>请输入手机号:<input type="text"></div>
<div>请输入验证码:<input type="text"></div>
</div>
`,
created: function(){
console.log('phoneLogin') // 2.用来说明渲染了phoneLogin组件,多次渲染则多次输出
}
})
var vm = new Vue({
el: '#app',
data: {
loginType:'userInfoLogin'
},
methods: {
changeLogin: function() {
this.loginType = this.loginType === 'userInfoLogin'?'phoneLogin':'userInfoLogin';
}
}
})
</script>
补充
- 没有写的内容:异步组件
$refs
- 有时候你可能需要获取某个元素来进行操作,在没有学refs之前,你可能需要document.xxx来获取某个元素。
- 学了refs之后,你可以使用refs来获取元素
使用步骤:
1.在元素中使用ref属性给元素起一个有标识意义的名字。
2.this.\(refs可以获取当前组件实例的所有使用了ref的元素,`this.\)refs.名字`代表指定的元素。
3.然后你可以进行dom操作了。
<body>
<div id="app">
<div ref='demo'>1</div> <!-- 1. 设置ref -->
<button @click='changeColor'>+1</button>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el: '#app',
methods: {
changeColor:function() {
console.log(this.$refs.demo.innerText) // 获取ref对象,有了dom对象你就可以进行dom操作了。
this.$refs.demo.style = 'color:red'
this.$refs.demo.innerText+=this.$refs.demo.innerText
}
}
})
</script>
获取组件的引用
refs也可以用在组件上,可以获取组件的数据(但这时候的ref获取的不是一个dom对象,而是一个vue实例对象,所以不能获取innerText这类dom属性)。
<body>
<div id="app">
<child ref='demo'></child>
<button @click='changeColor'>+1</button>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child',{
data: function(){
return {
msg:'hello world!'
}
},
template:'<div>hello</div>'
})
var app = new Vue({
el: '#app',
methods: {
changeColor:function() {
console.log(this.$refs.demo.msg) // 获取组件实例的数据。
}
}
})
</script>
动画效果
如果说前面讲的都是基础,必须要掌握的话,那么动画效果是锦上添花,可有可无(对于我这些不追求动画效果就不显得重要了),所以这里就不讲了,这里仅仅是为了显示vue能有实现动画效果的功能。
有兴趣的可以看一下官网:
vue官网动画效果直通车