第7 章 组件详解(要重读)
7.1 组件与复用
7.1.1 为什么使用组件
Vue.js组件就是提高重用性的。
7.1.2 组件用法
组件需要注册后才可以使用,注册有全局注册和局部注册两种方式。
template的DOM结构必须被一个元素包含。
在使用data时,和实例稍有区别,data必须是函数,然后将数据return出去。
7.2 使用props传递数据
7.2.1 基本用法
传递数字、布尔值、数组、对象,如果不适用v-bind,传递的仅仅是字符串。
7.2.2 单向数据流
通过props传递数据是单向的了。
当使用DOM模板时,驼峰命名的props名称要转为短横分隔命名。
需要修改prop的情况:
一种是父组件传递初始值进来,子组件将它作为初始值保存起来,在组件的作用域下可以随意使用和修改;
另一种情况就是prop作为需要被转变的原始值传入,用计算属性。
7.2.3 数据验证
当prop需要验证时,就需要对象写法。
在浏览器控制台弹出警告,有什么用?
7.3 组件通信
7.3.1 自定义事件
当子组件需要向父组件传递数据时,就要用到自定义事件。子组件用
emit()来触发事件,父组件用
e
m
i
t
(
)
来
触
发
事
件
,
父
组
件
用
on()来监听子组件的事件。
父组件也可以直接在子组件的自定义标签上使用v-on来监听子组件触发的自定义事件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
{{ total }}
<my-component @increase="handleGetTotal" @reduce="handleGetTotal"></my-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component("my-component",{
template : '\
<div>\
<button @click="handleIncrease">+1</button>\
<button @click="handleReduce">-1</button>\
</div>',
data : function () {
return {
counter : 0
}
},
methods : {
handleIncrease : function () {
this.counter++;
this.$emit('increase',this.counter);
},
handleReduce : function () {
this.counter--;
this.$emit('reduce',this.counter);
}
}
});
var app = new Vue({
el : "#app",
data : {
total : 0
},
methods : {
handleGetTotal : function (total) {
this.total = total;
}
}
});
</script>
</body>
</html>
7.3.2 使用v-model
在自定义组件上使用v-model指令。
组件$emit()的事件名是特殊的input,在使用组件的父级,并没有使用@input=”handler”,而是直接用了v-model绑定的一个数据total。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
{{ total }}
<my-component v-model="total"></my-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
template : '\
<div>\
<button @click="handleIncrease">+1</button>\
</div>',
data : function () {
return {
counter : 0
}
},
methods : {
handleIncrease : function () {
this.counter++;
this.$emit('input',this.counter);
}
}
});
var app = new Vue({
el : "#app",
data : {
total : 0
}
});
</script>
</body>
</html>
7.3.3 非父子组件通信
1)中央事件总线bus
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
{{ message }}
<my-component></my-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var bus = new Vue();
Vue.component('my-component',{
template : '\
<button @click="handleEvent">传递事件</button>\
',
methods : {
handleEvent : function () {
bus.$emit("on-message","this message come from my-component");
}
}
});
var app = new Vue({
el : "#app",
data : {
message : 'init'
},
mounted : function () {
var _this = this;
bus.$on("on-message",function (msg) {
_this.message = msg;
});
}
});
</script>
</body>
</html>
这种方法实现了任何组件间的通信,可以扩展bus实例,给它添加data、methods、computed等选项,实现信息共享(用户登录信息、授权token等)
2)父链
子组件使用this.
parent可以直接访问该组件的父实例或组件,父组件使用this.
p
a
r
e
n
t
可
以
直
接
访
问
该
组
件
的
父
实
例
或
组
件
,
父
组
件
使
用
t
h
i
s
.
children访问它所有的子组件,而且可以递归向上或向下无限访问。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
{{ message }}
<my-component></my-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
//var bus = new Vue();
Vue.component('my-component',{
template : '\
<button @click="handleEvent">传递事件</button>\
',
methods : {
handleEvent : function () {
//bus.$emit("on-message","this message come from my-component");
this.$parent.message="this message come from my-component through $parent";
}
}
});
var app = new Vue({
el : "#app",
data : {
message : 'init'
}
/*,
mounted : function () {
var _this = this;
bus.$on("on-message",function (msg) {
_this.message = msg;
});
}*/
});
</script>
</body>
</html>
3)子组件索引
在父组件模板中,子组件标签上使用ref指定一个名称,并在父组件内通过this.$refs来访问指定名称的子组件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<button @click="getChildMsg">获得子组件内容</button>
<component-a ref="comA"></component-a>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('component-a',{
template : '<div>子组件</div>',
data : function () {
return {
message : '子组件内容'
}
}
});
var app = new Vue({
el : '#app',
methods : {
getChildMsg : function () {
var message = this.$refs.comA.message;
alert(message);
}
}
});
</script>
</body>
</html>
7.4 使用slot分发内容
7.4.1 什么是slot
当需要让组件组合使用,混合父组件的内容和子组件的模板时,就会用到slot,这个过程叫作内容分发。
props传递参数、events触发事件和slot内容分发就构成了Vue组件的3个API来源。
7.4.2 作用域
父组件模板的内容是在父组件作用域内编译,子组件模板的内容是在子组件作用域内编译。
7.4.3 slot用法
1)单个slot
在子组件内使用特殊的元素就可以为这个子组件开启一个slot(插槽),在父组件模板里,插入在子组件标签内的所有内容将替代子组件的标签及它的内容。
2)具名slot
7.4.4 作用域插槽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<my-list :books="books">
<template slot="book" scope="props">
<li>{{props.bookName}}</li>
</template>
</my-list>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-list',{
props : {
books : {
type : Array,
default : function () {
return [];
}
}
},
template : '\
<ul>\
<slot name="book"\
v-for="book in books"\
:book-name="book.name">\
</slot>\
</ul>'
});
var app = new Vue({
el : '#app',
data : {
books : [
{ name : '<<test>>'},
{ name : '<<test2>>'},
{ name : '<<test3>>'}
]
}
});
</script>
</body>
</html>
7.4.5 访问slot
通过
slots可以访问某个具名slot,this.
s
l
o
t
s
可
以
访
问
某
个
具
名
s
l
o
t
,
t
h
i
s
.
slots.default包括了所有没有被包含在具名slot中的节点。
7.5 组件高级用法
7.5.1 递归组件
7.5.2 内联模板
7.5.3 动态组件
7.5.4 异步组件
7.6 其他.
7.6.1 $nextTick
7.6.2 X-Templates
7.6.3 手动挂载实例
7.7 实战
例子1:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数字输入框组件</title>
</head>
<body>
<div id="app">
<input-number v-model="value" :max="10" :min="0"></input-number>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script src="input-number.js"></script>
<script src="index.js"></script>
</body>
</html>
var app = new Vue({
el : '#app',
data : {
value : 5
}
});
function isValueNumber (value) {
return (/(^-?[0-9]+\.{1}\d+$)|(^-?[1-9][0-9]*$)|(^-?0{1}$)/).test(value + '');
}
Vue.component('input-number',{
template : '\
<div class="input-number">\
<input\
type="text"\
:value="currentValue"\
@change="handleChange">\
<button\
@click="handleDown"\
:disabled="currentValue <= min">-</button>\
<button\
@click="handleUp"\
:disabled="currentValue >= max">+</button>\
</div>',
props : {
max : {
type : Number,
default : Infinity
},
min : {
type : Number,
default : -Infinity
},
value : {
type : Number,
default : 0
}
},
data : function () {
return {
currentValue : this.value
}
},
watch : {
currentValue : function (val) {
this.$emit('input',val);
this.$emit('on-change',val);
},
value : function (val) {
this.updateValue(val);
}
},
methods : {
handleDown : function () {
if(this.currentValue <= this.min) return;
this.currentValue -= 1;
},
handleUp : function () {
if(this.currentValue >= this.max) return;
this.currentValue += 1;
},
handleChange : function (event) {
var val = event.target.value.trim();
var max = this.max;
var min = this.min;
if(isValueNumber(val)) {
val = Number(val);
this.currentValue = val;
if(val > max) {
this.currentValue = max;
} else if( val < min) {
this.currentValue = min;
}
} else {
event.target.value = this.currentValue;
}
},
updateValue : function (val) {
if( val > this.max ) val = this.max;
if( val < this.min ) val = this.min;
this.currentValue = val;
}
},
mounted : function () {
this.updateValue(this.value);
}
});
;
例子二: