1. 认识组件
1.1
基础示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<script>
/*
1. 组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 <button-counter>;
我们可以在一个通过new Vue创建的Vue根实例中,把这个组件作为自定义标签来使用;
2. 因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,
例如 data、computed、watch、methods 以及生命周期钩子等;
3. 你可以将组件进行任意次数的复用;注意当点击按钮时,每个组件都会各自独立维护它的 count;
因为你每用一次组件,就会有一个它的新实例被创建;
4. data 必须是一个函数;
当我们定义这个 <button-counter> 组件时,你可能会发现它的 data 并不是像这样直接提供一个对象:
data: {
count: 0
}
取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function() {
return {
count: 0
}
}
如果 Vue 没有这条规则,点击一个按钮就可能会像如下代码一样影响到其它所有实例:
也就是说,此时这几个组件共同使用count这个属性, 数据共享, 一改全改!
*/
</script>
</head>
<body>
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script type="text/javascript">
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: `<button v-on:click="count++">you clicked me {{count}} times!</button>`
});
var vm = new Vue({
el: '#app'
});
</script>
</body>
</html>
1.2
全局注册和局部注册
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<script>
/*
0. 局部组件:在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件;
var ComponentA = { ... };
var ComponentB = { ... };
var ComponentC = { ... };
然后在 components 选项中定义你想要使用的组件:
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
} 属性名就是组件的名字!!!
});
1. 组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用;
仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树;
2. 所有实例都能用全局组件;
也可以在实例选项中注册局部组件,这样组件只能在这个实例中使用。
比如在app的Vue实例中分别注册了全局组件和局部组件,
此时,我们又创建了另一个APP的Vue实例,在APP的Vue实例和app的Vue实例中我们都可以使用全局组件global-item,
但是不能在APP的Vue实例中使用 在app的Vue实例中注册局部组件local-item!
*/
</script>
</head>
<body>
<!-- app的Vue实例 -->
<div id="app">
{{message}}
<global-item></global-item>
<local-item></local-item>
</div>
<hr>
<!-- APP的Vue实例 -->
<div id="APP">
{{message}}
<global-item></global-item>
<local-item></local-item> <!-- 在App实例下使用app实例下的局部组件, 默默失败(之前我记得是会报错的, 现在不报错了) -->
</div>
<script>
// 全局组件
Vue.component('global-item', {
template: '<h4>全局组件</h4>'
});
// 局部组件(挂载到app实例下)
var item = {
template: `<h4>挂载在app实例下的局部组件</h4>`
};
// app
var app = new Vue({
el: '#app',
data: {
message: 'app实例',
},
components: {
'local-item': item
}
});
// App
var APP = new Vue({
el: "#APP",
data: {
message: 'APP实例'
}
});
</script>
</body>
</html>
1.3
template模板抽离
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<!--
通过template标签注册局部组件(id选择器);
这样就把原来写在template中的一坨代码给抽离了出来,并且此时可以语法高亮,而且更易于维护!!!
强烈建议以后写在组件中的template中的代码单独将其抽离出来写!!!(通过id选择器)
-->
</head>
<body>
<!-- [1].以下这样也可以注册组件内容:但是自身也会占有一个标签 -->
<!-- <div id="loginTemplate">
<h1>登录</h1>
</div> -->
<!-- [2].通过template标签注册组件内容: template标签起包裹作用; -->
<template id="loginTemplate">
<h1>登录</h1>
</template>
<div id="app">
<login-item></login-item>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
components: {
'login-item': {
template: `#loginTemplate`
}
}
});
</script>
</body>
</html>
1.4
根组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<!--
实际上这个Vue实例也可以看做是一个根组件, 既然是组件,那么必然是可以有template属性了!
-->
</head>
<body>
<div id="app">
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
},
methods: {
},
template: `<h1>HelloWorld</h1>` /* template属性 */
});
</script>
</body>
</html>
1.5
单向数据流
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<!--
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:
父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。
这意味着你不应该在一个子组件内部改变 prop。
如果你这样做了,Vue 会在浏览器的控制台中发出警告。
-->
<!--
这里有两种常见的试图变更一个 prop 的情形:
1. 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。
在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function() {
return {
counter: this.initialCounter
}
}
2. 这个 prop 以一种原始的值传入且需要进行转换。
在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props:['size'],
computed: {
normalizedSize: function() {
return this.size.trim().toLowerCase();
}
}
-->
<!--
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,
在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
-->
</head>
<body>
<div id="app">
<login-item :attr='father'></login-item>
</div>
<script type="text/javascript">
/*
我们通过props-attr属性确实拿到了父组件中的father属性,
与此同时我们又定义了自己的数据属性attr, 从而发生了错误!!!
这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue会在浏览器的控制台中发出警告;
可以通过this.name来保存来自父组中的father数据:
data() {
return {
data: this.attr,
}
}
*/
const item = {
template: `<div>{{attr}} <***> {{}}</div>`,
props: ['attr'],
data() {
return {
attr: 'i am son',
// data: this.attr,
}
}
};
var vm = new Vue({
el: '#app',
data: {
father: 'i am father'
},
components: {
'login-item': item
}
});
</script>
</body>
</html>
2. 组件注册
2.1
全局组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js"></script>
</head>
<body>
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script type="text/javascript">
// 组件注册:
Vue.component('button-counter', {
// data必须是一个函数
data: function () {
return {
count: 0
}
},
// 组件模板:如下所示, 如果不存在div, 那么测试按钮不能被渲染出来(之前我记得是:如果不包含div, 那么直接报错, 现在倒是不报错了)
// 也即是如果template中有多个元素, 必须使用一个根元素来包裹其内容, 这个根元素通常是div;
template: `
<div>
<button @click="handle">点击了{{count}}次</button>
<button>测试</button>
</div>
`,
// 组件方法
methods: {
handle: function () {
this.count += 2;
}
}
});
//
var vm = new Vue({
el: '#app'
});
</script>
</body>
</html>
2.2
局部组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js"></script>
<!--
可以通过一个普通的 JavaScript 对象来定义组件; 然后在 components 选项中定义你想要使用的组件;
var ComponentA = { ... };
var ComponentB = { ... };
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
});
局部组件只能在注册它的父组件中使用; 别的地方把不可以使用;
-->
</head>
<body>
<div id="app">
<tom-com></tom-com>
<jerry-com></jerry-com>
</div>
<script type="text/javascript">
// 局部组件
var tom = {
data: function () {
return {
msg: 'helloTom'
}
},
template: '<div>{{msg}}</div>'
};
var jerry = {
data: function () {
return {
msg: 'helloJerry'
}
},
template: '<div>{{msg}}</div>'
};
//
var vm = new Vue({
el: '#app',
data: {},
methods: {},
components: {
'tom-com': tom,
'jerry-com': jerry
/* tom 和 jerry不要加引号,否则无法无法正常显示, tom 和 jerry是变量 */
/* tom-com 和 jerry-com是组件的名字 */
}
});
</script>
</body>
</html>
2.3
组件命名规则
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js"></script>
<!--
1. 短横线: 当用短横线(字母全小写且必须包含一个连字符)定义组件时, 那么引用该组件时必须使用短横线形式; (<my-component-name>)
2. 驼峰: 当用驼峰(大驼峰)定义组件时, 那么引用该组件时两种命名法都可以;(<my-component-name> 或 <MyComponentName>)
但是如果直接在DOM元素中使用驼峰命名的组件时, 必须以短横线形式来进行引用;
如下所示:
a. HelloWorld组件是大驼峰形式命名的;
此时在button-counter组件中使用HelloWorld组件, 因为是在template模板字符串中包裹, 此时用HelloWorld 和 hello-world两种形式都可以;
但是如果是在<div id="app">中使用, 那么此时对于HelloWorld组件来说, 必须使用短横线形式, 否则无效;
b. 综上所述, 后续为了避免这种麻烦事, 组件命名一律采用短横线形式;
-->
</head>
<body>
<div id="app">
<button-counter></button-counter>
<HelloWorld></HelloWorld> <!-- 在DOM中使用驼峰名, 无效 -->
<hello-world></hello-world> <!-- 必须使用短横线形式 -->
</div>
<script type="text/javascript">
Vue.component('HelloWorld', {
data: function() {
return {
msg: 'HelloWorld'
}
},
template: `<div>{{msg}}</div>`
});
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: `
<div>
<button @click="handle">点击了{{count}}次</button>
<button>测试</button>
<HelloWorld></HelloWorld>
<hello-world></hello-world>
</div>
`,
methods: {
handle: function () {
this.count += 2;
}
}
});
//
var vm = new Vue({
el: '#app'
});
</script>
</body>
</html>
2.4
单个根元素
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<!--
单个根元素:
当构建一个 <blog-post> 组件时,你的模板最终会包含的东西远不止一个标题:
template: `<h3> {{ title }} </h3>`
最起码,你会包含这篇博文的正文:
template: `
<h3> {{ title }} </h3>
<div v-html="content"></div>
`
然而如果你在模板中尝试这样写,Vue会显示一个错误,
并解释道 every component must have a single root element (每个组件必须只有一个根元素)。
解决方案:
你可以将模板的内容包裹在一个父元素内,来修复这个问题,例如:
template:
`
<div class="content">
<h3> {{title}} </h3>
<div v-html="content"></div>
</div>
`
-->
<!--
之前控制台会报错提示, 现在没有提示了, 只是默默的失败;
-->
</head>
<body>
<div id="app">
<global-item></global-item>
</div>
<script type="text/javascript">
Vue.component('global-item', {
template: `
<div>
<h4>HelloWorld</h4>
<h4>WorldHello</h4>
</div>
`
});
var vm = new Vue({
el: '#app'
});
</script>
</body>
</html>
3. props属性
3.1
props属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js"></script>
<!--
《摘自官网》
Prop 是你可以在组件上注册的一些自定义 attribute;
当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property;
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop;
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
});
在上述模板中,你会发现我们能够在组件实例中访问这个值,就像访问 data 中的值一样;
<blog-post v-for="post in posts" v-bind:key="post.id" v-bind:title="post.title" ></blog-post>
如上所示,你会发现我们可以使用 v-bind 来 ```动态传递 prop ```;
-->
</head>
<body>
<div id="app">
<!--
在这里我们给自定义组件的属性todo绑定了一个对象item,
然后在自定义组件中通过props这个属性进行获取这个绑定的对象,然后对这个获取的对象进行操控;
``````此时我们就在子组件中拿取到了父组件中的值; ``````
-->
<self-com v-for="item in shopList" :todo="item" v-bind:key="item.id"></self-com>
</div>
<script>
/* props就是一个存放组件中自定义属性的数组; */
/* 通过此属性,我们将父作用域中的数据传到了子组件中; */
Vue.component('self-com', {
props: ['todo'],
template: ` <li> {{ todo.text + '---id:'+ todo.id }}</li> `
});
//
var app = new Vue({
el: '#app',
data: {
shopList: [{
id: 0,
text: '蔬菜'
}, {
id: 1,
text: '奶酪'
}, {
id: 2,
text: '酒'
}
]
}
});
</script>
</body>
</html>
3.2
props类型
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js"></script>
</head>
<body>
<!--
props属性值类型:
String, Number, Boolean, Array, Object
说白了,这个props里面存储的就是它这个自定义组件本身的属性名,然后在template里面将这属些性值打印出来!!!
-->
<div id="app">
<self-item v-bind:str='string' v-bind:num='12' :bol='false' :arr='array' :obj='object'></self-item>
</div>
<script type="text/javascript">
Vue.component('self-item',{
props: ['str','num','bol','arr','obj'],
template: `
<div>
<div>{{str}}</div>
<div>{{num + 24}}</div>
<div>{{bol}}</div>
<ul>
<li :key="index" v-for="(item,index) in arr">{{item}}</li>
</ul>
<div>
<span>{{obj.name + " " + obj.age}}</span>
</div>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
string: '我是String',
array: ['apple','pear','banana'],
object: {
name: 'liSi',
age: 23
}
}
});
</script>
</body>
</html>
3.3
props类型校验
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<!--
1. 到这里,我们只看到了以字符串数组形式列出的 prop:
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
2. 但是,通常你希望每个 prop 都有指定的值类型,
这时,你可以以对象形式列出 prop,
这些 property 的名称和值分别是 prop 各自的名称和类型:
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
这不仅为你的组件提供了文档,还会在它们遇到错误的类型时从浏览器的 JavaScript 控制台提示用户。
3. 注意:在下面的例子中,因为props中的属性全部是驼峰, 所以标签中要使用小写短横线形式, 否则ERROR!(ps: 在此处浪费诸多时间)
-->
</head>
<body>
<div id="app">
<self-com
:prop-a="100"
:prop-b="12"
:prop-c="'Xing'"
:prop-d=""
:prop-e="{name: 'Wei'}"
:prop-f="'success1'"
:prop-g="12"
></self-com>
</div>
<script type="text/javascript">
Vue.component('self-com',{
props: {
propA: Number,
propB: [String, Number], /* propB: 多种类型 */
propC: { /* propC: 必传且必须是字符串 */
type: String,
required: true
},
propD: { /* propD: 有默认值的数字 */
type: Number,
default: 100
},
propE: {
type: Object,
default: function() {
return {
message: 'HelloWorld'
}
}
},
propF: { /* 自定义验证函数 */
validator: function(value) {
return ['success','warning','danger'].indexOf(value) !== -1;
}
},
propG: {
validator: function(value) {
return value < 100;
}
}
},
template: `
<div>
<p>{{propA}}</p>
<p>{{propB}}</p>
<p>{{propC}}</p>
<p>{{propD}}</p>
<p>{{propE}}</p>
<p>{{propF}}</p>
<p>{{propG}}</p>
</div>
`
})
var vm = new Vue({
el: '#app'
});
</script>
</body>
</html>
3.4
props属性名规则1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<!--
props属性名规则:
(1)在DOM中, 如果在props中使用的是属性名的驼峰形式, 那么必须在DOM中的标签中使用该属性的短横线形式, 否则默默的失败!
(2)当然, 如果是在字符串模板中使用,则没有这个限制;
=> 这和组件命名规则是相同的;
=> 为了省去不必要的麻烦, 后续一律采用短横线形式;
-->
</head>
<body>
<div id="app">
<!-- 1. props中的属性MenuTitle为驼峰形式, 此时在DOM的标签中该属性必须使用短横线形式, 否则默默的失败!!! -->
<menu-item MenuTitle="HelloWorld"></menu-item>
<!-- 2. props中的属性MenuTitle为驼峰形式, 此时在DOM,,,,,,使用了短横线形式, 正确!!! -->
<menu-item menu-title="HelloWorld"></menu-item>
</div>
<script type="text/javascript">
Vue.component('menu-item', {
props: ['MenuTitle'],
template: `
<div>
<h1> {{ MenuTitle }} </h1>
</div>
`
});
var vm = new Vue({
el: '#app'
});
</script>
</body>
</html>
3.5
props属性名规则2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
</head>
<body>
<!--
当然, 如果是在字符串模板中使用,则没有这个限制;
-->
<div id="app">
<menu-item menu-title="HelloWorld"></menu-item>
</div>
<script type="text/javascript">
Vue.component('third-com',{
props: ['testTitle'],
template:'<div>{{ testTitle }}</div>'
})
Vue.component('menu-item', {
props: ['menuTitle'],
template: `
<div>
{{ menuTitle }}
<third-com testTitle="hello"></third-com>
</div>`
});
//
var vm = new Vue({
el: '#app'
});
</script>
</body>
</html>
3.6
传递静态或动态props
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../vue.js" type="text/javascript"></script>
<script src="../vue-router.js"></script>
<!--
1. 像这样,你已经知道了可以像这样给 prop 传入一个静态的值:
<blog-post title="My journey with Vue"></blog-post>
2. 你也知道 prop 可以通过 v-bind 动态赋值,例如:
// 动态赋予一个变量的值
<blog-post v-bind:title="post.title"></blog-post>
// 动态赋予一个复杂表达式的值
<blog-post v-bind:title="post.title + 'by' + post.author.name"></blog-post>
在上述两个示例中,我们传入的值都是字符串类型的,但实际上任何类型的值都可以传给一个 prop。
-->
<!--
传入一个数字: <blog-post v-bind:likes="42"></blog-post>
传入一个布尔值:
(静)<blog-post is-published></blog-post>
(静)<blog-post v-bind:is-published="false"></blog-post>
(动态传递)<blog-post v-bind:is-published="post.isPublished"></blog-post>
-->
<!--
传入一个数组:
(静态传递)<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
(动态传递)<blog-post v-bind:comment-ids="post.commentIds"></blog-post>
-->
<!--
传入一个对象:
(静)<blog-post v-bind:author="{name: 'Veronica',company: 'Veridian Dynamics'}"></blog-post>
(动)<blog-post v-bind:author="post.author"></blog-post>
-->
<!--
传入一个对象的所有 property:如果你想要将一个对象的所有 property 都作为 prop 传入,你可以使用不带参数的 v-bind (取代 v-bind:prop-name)。
例如,对于一个给定的对象 post:
post: {
id: 1,
title: 'My Journey with Vue'
}
下面的模板:
<blog-post v-bind="post"></blog-post>
等价于:
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>
-->
</head>
<body>
<div id="app">
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
},
methods: {
}
});
</script>
</body>
</html>
4. 事件发射与监听
4.1
监听子组件事件1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../vue.js" type="text/javascript"></script>
<!--
父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递出去,就需要使用自定义事件;
我们可以使用 v-on 绑定自定义事件, 每个 Vue 实例都实现了事件接口(Events interface),即:
使用 $on(eventName) 监听事件;
使用 $emit(eventName) 触发事件;
另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件;
以下实例中子组件已经和它外部完全解耦了。它所做的只是触发一个父组件关心的内部事件。
-->
<!--
以下案例中:
当点击子组件时, 将会执行incrementHandler函数, 而在该事件处理函数中会发射add事件,
在根组件中, 我们对add事件进行了监听, 一旦截获到add事件, 那么就去执行sum处理函数;
-->
</head>
<body>
<div id="app">
<p> {{ total }}</p>
<button-counter v-on:add="sum"></button-counter>
</div>
<script type="text/javascript">
Vue.component('button-counter', {
data: function () {
return {
counter: 0
}
},
template: `<button v-on:click="incrementHandler">{{ counter }}</button>`,
methods: {
incrementHandler: function () {
this.counter += 1;
this.$emit('add'); /* 触发add事件 */
}
}
});
//
var vm = new Vue({
el: '#app',
data: {
total: 0,
},
methods: {
sum: function () {
this.total += 1;
}
}
});
</script>
</body>
</html>
4.2
监听子组件事件2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<!--
监听子组件事件:
子组件中使用$emit('事件名称', args...)触发事件;
在父组件中通过v-on:事件名称 来监听子组件触发的事件;
-->
</head>
<body>
<div id="app">
<div :style='{fontSize: size + "px"}'>{{ str }}</div>
<hr>
<!-- 1. 通过v-on来监听component-item组件的enlarge-text事件, 一旦监听到, 就立即执行 handle函数进行处理; -->
<component-item :attr='Attr' v-on:enlarge-text='handle'></component-item>
<!-- 2. 传递参数时: 通过用```$event```来截获子组件emit的事件所传递过来的参数值; -->
<component-item :attr='Attr' v-on:enlarge-text='handle($event)'></component-item>
</div>
<script type="text/javascript">
/*
点击此组件时, 将会触发enlarge-text这个事件,并且传递了一个参数5,
在父组件(#app)中通过v-on来监听该事件;
*/
Vue.component('component-item', {
props: ['attr'],
template: `<button @click='$emit("enlarge-text", 5)'>自定义组件/button> `
});
//
var vm = new Vue({
el: '#app',
data: {
Attr: 'love',
str: 'HelloWorld',
size: 10
},
methods: {
/* val就是子组件传递过来的参数 */
handle: function (val) {
console.log(val);
this.size += val;
}
}
});
</script>
</body>
</html>
4.3
兄弟组件之间的交互
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<!--
兄弟组件的交互:
1. 通过事件中心进行交互
var eventHub = new Vue();
2. 监听事件与移除事件监听
eventHub.$on('事件名称',处理函数)
eventHub.$off('事件名称')
3. 触发事件
eventHub.$emit('事件名称',id)
-->
</head>
<body>
<div id="app">
<bother1-com></bother1-com>
<hr>
<bother2-com></bother2-com>
<hr>
<button @click="destroy">移除事件监听</button>
</div>
<script type="text/javascript">
// 定义事件中心
var hub = new Vue();
// 兄弟组件1
Vue.component('bother1-com', {
data: function () {
return {
num: 0
}
},
template: `
<div>
<div>bother1: {{num}}</div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function () {
hub.$emit('bother2-event', 1); /* 给兄弟组件发射事件信号 */
}
},
mounted() { // 生命周期钩子函数
//我的事件处理函数
hub.$on('bother1-event', (val) => {
this.num += val; /*val是兄弟组件传过来的值 */
});
}
});
// 兄弟组件2
Vue.component('bother2-com', {
data: function () {
return {
num: 0
}
},
template: `
<div>
<div>bother2: {{num}}</div>
<div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function () {
hub.$emit('bother1-event', 2);
}
},
mounted() {
hub.$on('bother2-event', (val) => {
this.num += val;
})
}
});
//
var vm = new Vue({
el: '#app',
data: {},
methods: {
destroy: function () {
hub.$off('bother1-event');
hub.$off('bother2-event');
}
}
});
</script>
</body>
</html>
5. 插槽
5.1 初识插槽1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<!--
插槽: 用来插文本的槽!一个预留的占位符! <slot></slot>
-->
</head>
<body>
<div id="app">
<!--
1. 如果<alert-box>组件的 template中没有包含一个 <slot> 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃;
2. 组件中的内容交由slot处理渲染:
比如 <alert-box>存在问题</alert-box>,
那么由于在组件template中存在插槽slot, 所以```存在问题```将会被slot给渲染;
否则(没有slot), 那么诸如```存在问题```等内容不能被渲染出来;
-->
<alert-box>存在问题</alert-box>
<alert-box>存在警告</alert-box>
<alert-box></alert-box>
<alert-box>
<span>HelloWorld</span>
I know your name!
</alert-box>
</div>
<script type="text/javascript">
Vue.component('alert-box', {
template: `
<div>
<strong>ERROR:</strong>
<slot>default</slot>
</div>
`
});
var vm = new Vue({
el: '#app'
});
</script>
</body>
</html>
5.2 初始插槽2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<style>
.current {
color: red;
}
</style>
<!--
如下所示:在<child>组件中的内容 <h2>好吧</h2>等 实际上并不会被渲染出来;
<child>
<h2>好吧</h2>
</child>
也就是说:在组件<child></child>中的内容并不会被渲染出来, 它只会渲染<child>组件中template中的内容;
要想<child>组件中的内容也被渲染出来, 除非在组件<child>中的template中预留一个<slot></slot>占位符;
<div>
<p>HelloWorld</p>
<slot></slot>
</div>
-->
<!--
<child>
<h2>好吧</h2>
<h2>好吧</h2>
<h2>好吧</h2>
</child>
template: `
<div>
<strong>HelloWorld</strong>
<slot>default</slot>
</div>
`
此时好比将<child>组件中template中的<slot>标签给替换成了<child>的内容(3个h2标签);
-->
<!--
插槽可以提供一个默认内容,如果如果<child>组件没有为这个插槽提供了内容,会显示默认的内容;
如果<child>组件为这个插槽提供了内容,则默认的内容会被替换掉;
<child></child> // child组件没有提供插槽内容
<child> <h1>Ren Min</h1> </child> // child组件提供插槽内容
-->
</head>
<body>
<div id="ok">
<child>
<h2>好吧</h2>
<h2>好吧</h2>
<h2>好吧</h2>
</child>
<child></child>
</div>
<script type="text/javascript">
Vue.component('child', {
template: `
<div>
<strong>HelloWorld</strong>
<slot>default</slot>
</div>
`
});
var vm = new Vue({
el: '#ok'
});
</script>
</body>
</html>
5.3 具名插槽1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<!--
具名插槽:就是有名字的插槽!
当需要多个插槽时,可以使用<slot>标签的属性:name; 这个特性可以用来定义额外的插槽使用方法;
-->
<!--
<p slot='head'>头部内容</p>
也即是表达向名为head的插槽传递<p>这个标签数据;
-->
</head>
<body>
<div id="app">
<h1>--------第一种引用方式:--------</h1>
<self-com>
<p slot='head'>头部内容</p>
<p>中间内容1</p>
<p>中间内容2</p>
<p slot="foot">底部内容</p>
</self-com>
<h1>------第二种引用方式:----------</h1>
<self-com>
<template slot="head">
<p>头部内容</p>
</template>
<p>中间内容1</p>
<p>中间内容2</p>
<template slot="foot">
<p>底部内容</p>
</template>
</self-com>
</div>
<script type="text/javascript">
Vue.component('self-com', {
template: `
<div>
<header>
<div style="color: red">
<slot name='head'></slot>
</h1>
</header>
<center>
<slot></slot>
</center>
<footer>
<div style="color: green">
<slot name="foot"></slot>
</div>
</footer>
</div>
`
});
//
var vm = new Vue({
el: '#app'
});
</script>
</body>
</html>
5.4 具名插槽2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<script>
/*
slot和v-slot的结合使用:
1. <slot>元素有一个特殊的属性name, 这个属性可以用来定义额外的插槽;
一个不带name的<slot>出口会带有隐含的名字"default"; (<slot name="default"></slot>)
在向具名插槽提供内容的时候,我们可以在一个<template>元素上使用v-slot指令,并以v-slot的参数的形式提供其名称;
2. 在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以v-slot的参数的形式提供其名称;
现在<template>元素中的所有内容都将会被传入相应的插槽;
任何没有被包裹在带有v-slot的 <template>中的内容都会被视为默认插槽的内容;
3.
<template v-slot:header>
<h1>标题系列</h1>
</template>
也即是表达向名为header的插槽传递<h1></h1>这个标签数据;
实际上插槽就是一个空的占位符, 等待外物的到来, 然后将其渲染出来;
4. 注意 v-slot 只能添加在 <template> 上;
*/
</script>
</head>
<body>
<div id="app">
<base-layout>
<template v-slot:header>
<h1>标题系列</h1>
</template>
<template v-slot:default> <!-- 等价于<template> <p>文章的主要内容</p> </template> -->
<p>文章的主要内容</p>
</template>
<template v-slot:footer>
<h1>文章结尾部分</h1>
</template>
</base-layout>
</div>
<script type="text/javascript">
Vue.component('base-layout', {
template:`
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`
})
var vm = new Vue({
el: '#app'
});
</script>
</body>
</html>
5.5 作用域插槽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js" type="text/javascript"></script>
<style>
.current {
color: red;
}
</style>
<!--
应用场景:父组件对子组件的内容进行加工处理;
-->
<!--
作用域插槽(slot-scope属性):
首先我们给插槽<slot :info='item'>绑定了一个属性info;
<self-com>在渲染时, 会执行template中的内容, 实际上此时会生成3个li标签,
而3个li中又存在3个插槽<slot>, 并且该插槽绑绑定了一个动态属性info, 它的值是数组的项;
那么我们如何在外面拿取到info属性的值呢???
<template slot-scope="scope">
<h1>{{ scope.info }}</h1>
</template>
-->
</head>
<body>
<div id="ok">
<self-com :Array="array">
<template slot-scope='_scope'>
<!-- <strong>{{_scope.info}}</strong> -->
<strong v-if='_scope.info.id == 2' class="current">{{_scope.info}}</strong>
<i v-else style="font-weight: bold;">{{_scope.info}}</i>
</template>
</self-com>
</div>
<script type="text/javascript">
Vue.component('self-com', {
props: ['Array'],
template: `
<div>
<li v-for='item in Array' :key='item.id'>
<slot :info='item'>{{item.name}}</slot>
</li>
</div>
`
});
//
var vm = new Vue({
el: '#ok',
data: {
array: [{
id: 1,
name: 'apple'
}, {
id: 2,
name: 'orange'
}, {
id: 3,
name: 'banana'
}]
}
});
</script>
</body>
</html>