Web/11-Vue基础/Vue组件.md
2020-04-19 18:24:43 +08:00

619 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 子组件的定义和注册
我们在本文的第一段中通过`Vue.component`形式定义的是**全局组件**这一段中我们来讲一下**子组件**
### 在父组件中定义子组件
比如说一个`账号`模块是父组件里面分为`登陆`模块和`注册`模块这两个晓得模块就可以定义为子组件
需要注意的是作用域的问题我们在父组件中定义的子组件只能在当前父组件的模板中使用在其他的组件甚至根组件中都无法使用
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<account>
</account>
</div>
<script>
//定义、注册组件
Vue.component('account', {
template: '<div><h2>账号模块</h2> <login></login></div>',
components: {
'login': {
template: '<h3>登录模块</h3>'
}
}
});
new Vue({
el: '#app'
});
</script>
</body>
</html>
```
我们发现既然是定义父亲`<account>`的子组件那么子组件`<login>`的调用只能写在父组件`<account>``template`模板中
显示效果
![](http://img.smyhvae.com/20180423_1029.png)
### Vue 根实例中定义子组件
当然我们还可以这样做把整个 Vue 对象当成父亲这样的话就可以在 Vue 示例中定义一个子组件如下
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<login> </login>
</div>
<script>
new Vue({
el: '#app',
//在Vue实例中定义子组件
components: { // components 是关键字,不能改
'login': {
template: '<h3>登录模块</h3>'
}
}
});
</script>
</body>
</html>
```
这样写的话我们定义的子组件`<login>`在整个`#app`区域都是可以使用的
上面的代码还有**另外一种写法**把子组件的模板定义存放到变量中重要
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<login> </login>
</div>
<script>
//通过变量接收定义的子组件
var myLogin = {
template: '<h3>登录模块</h3>' // template 是关键字,不能改
}
new Vue({
el: '#app',
//在Vue实例中定义子组件
components: { // components 是关键字,不能改
'login': myLogin
}
});
</script>
</body>
</html>
```
注意在定义子组件时关键字`components`不要写错了
## 组件之间的动态切换暂略
我们可以利用`<component> `标签的`:is`参数来进行组件之间的切换
## 父组件向子组件传递数据
我们要记住父组件通过**属性**的形式向子组件传递数据
**引入**
我们先来看这样一段代码
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<script src="vue2.5.16.js"></script>
<body>
<div id="app">
<!-- 这里是父组件的范围,自定义一个数值 number -->
<counter number="1+2"> </counter>
<counter number="10+20"> </counter>
</div>
<script>
var myCounter = {
//【重要】这里是子组件的范围,无法直接获取父组件中的 number
template: '<div>我是子组件。{{number}}</div>'
}
var vm = new Vue({
el: '#app',
components: {
'counter': myCounter
}
});
</script>
</body>
</html>
```
上方代码中我想把父组件里 number 的数值传递给子组件直接这样写是看不到效果的
![](http://img.smyhvae.com/20180424_1520.png)
**1父组件传值给子组件**
要通过 props 属性将number进行传递给子组件才可以代码如下
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<script src="vue2.5.16.js"></script>
<body>
<div id="app">
<!-- 这里是父组件的范围,自定义一个数值 number -->
<counter :number="1+2"> </counter>
<counter :number="10+20"> </counter>
</div>
<script>
var myCounter = {
//这里是子组件的范围
props: ['number'], //通过 props 属性将父亲的 number 数据传递给子组件
template: '<div>我是子组件。{{number}}</div>'
}
var vm = new Vue({
el: '#app',
components: {
'counter': myCounter
}
});
</script>
</body>
</html>
```
`<counter>`标签中要注意`:number`里的冒号加上冒号那么引号里的内容就是表达式期望的结果否则引号的内容只是字符串
![](http://img.smyhvae.com/20180424_1530.png)
**2子组件获取了父组件的数据后进行求和操作**
上方代码中子组件已经获取了父组件的两个number现在要求每点击一次子组件在子组件中将数据加 1
一般人可能会这样写不推荐的写法子组件直接修改父组件中的数据
```javascript
var myCounter = {
//这里是子组件的范围
props: ['number'], //通过 props 属性将父亲的数据传递给子组件
template: '<div @click="addClick">我是子组件。{{number}}</div>',
methods: {
addClick: function () {
this.number ++; //这种写法不推荐。不建议直接操作父组件中的数据
}
}
}
```
上方代码的写法不推荐因为不建议直接操作父组件中的数据虽然数据操作成功但是控制台会报错
img.png
这样涉及到**单向数据流**的概念
- 父组件可以传递参数给子组件但是反过来子组件不要去修改父组件传递过来的参数因为同一个参数可能会传递给多个子组件避免造成修改的冲突
既然如此我可以把父组件中的数据在子组件中创建**副本**然后我们去修改这个副本就不会造成影响了最终完整版代码如下
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<script src="vue2.5.16.js"></script>
<body>
<div id="app">
<!-- 这里是父组件的范围,自定义一个数值 number -->
<counter :number="1+2"> </counter>
<counter :number="10+20"> </counter>
</div>
<script>
var myCounter = {
//这里是子组件的范围
props: ['number'], //通过 props 属性将父亲的数据传递给子组件
data: function () {
return {
number2: this.number
}
},
template: '<div @click="addClick">我是子组件。{{number2}}</div>',
methods: {
addClick: function () {
this.number2 ++; //操作和修改number的副本
}
}
}
var vm = new Vue({
el: '#app',
components: {
'counter': myCounter
},
});
</script>
</body>
</html>
```
## 子组件向父组件传值
我们要记住子组件通过**事件触发**的形式向父组件传值
**案例1:**子组件给父组件传递数据
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<!-- 在外层监听`mysend`事件,进而出发外层的 getData 方法 -->
<counter v-on:mysend="getData"> </counter>
</div>
<script>
Vue.component('counter', {
template: '<div @click = "addClick">发送数据给父组件</div>', //当组件被点击时,触发 addClick 方法
methods: {
addClick: function () {
//第一个参数为键(注意,要小写,不能大写),第二个参数为值
this.$emit('mysend', 'smyhvae'); //通过键`mysend`事件通知外面,将值`smyhvae`传给父组件
}
}
});
new Vue({
el: '#app',
methods: {
getData: function (input) { //通过括号里的参数,获取子组件传递过来的值
console.log(input); //打印结果smyhvae
}
}
});
</script>
</body>
</html>
```
**案例2**获取子组件的DOM对象
题目给两个相同的子组件定义计数器每点击一次数值加1然后在父组件中求和
步骤1给两个相同的子组件定义计数器每点击一次数值加1代码如下
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<counter> </counter>
<counter> </counter>
</div>
<script>
Vue.component('counter', {
template: '<div @click = "addClick">当前计数:{{number}}</div>', //当组件被点击时,调用 addClick 方法
data: function () {
return {
number: 0 //给组件定义一个数据number
}
},
methods: {
addClick: function () {
this.number++; //定义方法每点击一次number 的数值加 1
}
}
});
new Vue({
el: '#app'
});
</script>
</body>
</html>
```
步骤2两个子组件的数值加 1 通知父组件代码如下
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<!-- 在外层监听`change`事件,进而出发外层的 myClick 方法 -->
<counter @change="myMethod"> </counter>
<counter @change="myMethod"> </counter>
</div>
<script>
Vue.component('counter', {
template: '<div @click = "addClick">当前计数:{{number}}</div>', //当组件被点击时,
data: function () {
return {
number: 0 //给组件定义一个数据number
}
},
methods: {
addClick: function () {
this.number++; //定义方法每点击一次number 的数值加 1
this.$emit('change'); //通过这一行的`change`,通知外面,内部的 addClick 方法已经执行了
}
}
});
new Vue({
el: '#app',
methods: {
myMethod: function () {
console.log('触发父组件');
}
}
});
</script>
</body>
</html>
```
上方代码中通过关键字`emit`通知父组件子组件里的 addClick 方法被执行了父组件得知后执行`myMethod()`方法这个方法是在Vue实例中定义的很好理解
步骤3在父组件中求和
既然父组件已经得知子组件的 addClick 事件那我们直接在父组件的`myMethod()`里定义求和的方法即可
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<!-- 在外层监听`change`事件,进而触发外层的 myMethod 方法 -->
<counter ref="one" @change="myMethod"> </counter>
<counter ref="two" @change="myMethod"> </counter>
<div>{{totalData}}</div>
</div>
<script>
Vue.component('counter', {
template: '<div @click = "addClick">当前计数:{{number}}</div>', //当组件被点击时,触发 addClick方法
data: function () {
return {
number: 0 //给组件定义一个数据number
}
},
methods: {
addClick: function () {
this.number++; //定义方法每点击一次number 的数值加 1
this.$emit('change'); //通过这一行自定义的`change`,通知外面,内部的 addClick 方法已经执行了
}
}
});
new Vue({
el: '#app',
data: {
totalData: 0
},
methods: {
myMethod: function () {
console.log('触发父组件');
//通过`$refs`获取子组件中各自的number数值
var a1 = this.$refs.one.number;
var a2 = this.$refs.two.number;
//求和,存放在父组件的 totalData 中
this.totalData = a1 + a2;
console.log();
}
}
});
</script>
</body>
</html>
```
代码的关键
- `<counter>`标签中通过 `ref = "xxx"`属性给各个组件起一个别名代表组件的引用
- 在父函数`myMethod()`通过`this.$refs.xxx`获取组件的引用我们看一下最后两行代码在控制台的输出便知组件里有 number 属性
![](http://img.smyhvae.com/20180424_1455.png)