1
0
mirror of https://github.com/Daotin/Web.git synced 2024-10-29 12:04:46 +08:00
Web-main/12-Vue/05-组件.md

1500 lines
35 KiB
Markdown
Raw Permalink Normal View History

2019-12-22 16:29:51 +08:00
- [一、Vue组件](#%E4%B8%80vue%E7%BB%84%E4%BB%B6)
- [二、定义组件](#%E4%BA%8C%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6)
* [1、定义全局组件](#1%E5%AE%9A%E4%B9%89%E5%85%A8%E5%B1%80%E7%BB%84%E4%BB%B6)
* [2、定义局部组件](#2%E5%AE%9A%E4%B9%89%E5%B1%80%E9%83%A8%E7%BB%84%E4%BB%B6)
- [三、动态组件](#%E4%B8%89%E5%8A%A8%E6%80%81%E7%BB%84%E4%BB%B6)
- [四、组件间传值](#%E5%9B%9B%E7%BB%84%E4%BB%B6%E9%97%B4%E4%BC%A0%E5%80%BC)
* [1、父组件向子组件传值](#1%E7%88%B6%E7%BB%84%E4%BB%B6%E5%90%91%E5%AD%90%E7%BB%84%E4%BB%B6%E4%BC%A0%E5%80%BC)
+ [1.1、props类型验证](#11props%E7%B1%BB%E5%9E%8B%E9%AA%8C%E8%AF%81)
+ [1.2、props必填验证](#12props%E5%BF%85%E5%A1%AB%E9%AA%8C%E8%AF%81)
+ [1.3、props默认值](#13props%E9%BB%98%E8%AE%A4%E5%80%BC)
+ [1.4、props自定义匹配规则](#14props%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8C%B9%E9%85%8D%E8%A7%84%E5%88%99)
+ [1.5、prop 是单项数据流](#15prop-%E6%98%AF%E5%8D%95%E9%A1%B9%E6%95%B0%E6%8D%AE%E6%B5%81)
* [非 Prop 的特性](#%E9%9D%9E-prop-%E7%9A%84%E7%89%B9%E6%80%A7)
* [2、子组件向父组件传值](#2%E5%AD%90%E7%BB%84%E4%BB%B6%E5%90%91%E7%88%B6%E7%BB%84%E4%BB%B6%E4%BC%A0%E5%80%BC)
* [3、子组件之间相互传值](#3%E5%AD%90%E7%BB%84%E4%BB%B6%E4%B9%8B%E9%97%B4%E7%9B%B8%E4%BA%92%E4%BC%A0%E5%80%BC)
+ [3.1、Tips](#31tips)
- [三、组件切换](#%E4%B8%89%E7%BB%84%E4%BB%B6%E5%88%87%E6%8D%A2)
* [1、方式一](#1%E6%96%B9%E5%BC%8F%E4%B8%80)
* [2、方式二](#2%E6%96%B9%E5%BC%8F%E4%BA%8C)
- [四、组件传值](#%E5%9B%9B%E7%BB%84%E4%BB%B6%E4%BC%A0%E5%80%BC)
* [1、父组件向子组件传值](#1%E7%88%B6%E7%BB%84%E4%BB%B6%E5%90%91%E5%AD%90%E7%BB%84%E4%BB%B6%E4%BC%A0%E5%80%BC-1)
* [2、父组件向子组件传方法](#2%E7%88%B6%E7%BB%84%E4%BB%B6%E5%90%91%E5%AD%90%E7%BB%84%E4%BB%B6%E4%BC%A0%E6%96%B9%E6%B3%95)
* [3、使用 ref 获取DOM和组件的引用](#3%E4%BD%BF%E7%94%A8-ref-%E8%8E%B7%E5%8F%96dom%E5%92%8C%E7%BB%84%E4%BB%B6%E7%9A%84%E5%BC%95%E7%94%A8)
---
2018-09-04 20:53:38 +08:00
## 一、Vue组件
什么是组件: 组件的出现,就是为了拆分 Vue 实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可;
**组件化和模块化的不同:**
- 模块化: 是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一;
- 组件化: 是从UI界面的角度进行划分的前端的组件化方便UI组件的重用
## 二、定义组件
### 1、定义全局组件
2019-01-03 23:07:32 +08:00
通过 `Vue.component(cname,option)`函数来定义
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
其中cname为字符串类型 代表组件名option用于配置组件相关属性。
相关属性:例如:data computed methods template watch等等。
2018-09-04 20:53:38 +08:00
```js
2019-01-03 23:07:32 +08:00
// main.js
import Vue from 'vue'
// 全局组件
// 全局组件的定义一定要在new Vue之前。
// 定义之后到处可以使用,并且不需再声明
Vue.component('mydiv', {
//不能在顶层元素使用v-for因为顶层元素只能有一个
template: `
<div>
<div v-for="item in goods">{{item.name}}</div>
</div>
`,
data: function() {
return {
goods: [
{ name: 'Daotin', age: 18 },
{ name: 'lvonve', age: 19 },
{ name: 'wenran', age: 20 }
]
}
}
2018-09-04 20:53:38 +08:00
});
2019-01-03 23:07:32 +08:00
new Vue({
el: '#app',
data:{}
});
2018-09-04 20:53:38 +08:00
```
2019-01-03 23:07:32 +08:00
全局组件的使用:
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
```html
<!DOCTYPE html>
<html lang="en">
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
<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>
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
<body>
<div id="app"></div>
<!--全局组件的使用-->
<my-login></my-login>
</body>
</html>
2018-09-04 20:53:38 +08:00
```
2019-01-03 23:07:32 +08:00
> 注意:
>
> 0、不论是全局组件还是局部组件组件的 template 属性指向的模板内容,必须有且只能有唯一的一个顶层元素,否则会报错。
>
> 1、使用组件的时候双标签和单标签的方式都可以。`<my-login></my-login>` 或者 `<my-login />`
>
> 2、组件的内部除了有 `template`还有datamethodswatchcomputed等所有vue实例的属性。
>
> 3、使用 Vue.component 定义全局组件的时候,组件名称使用了 驼峰命名如myLogin则在调用组件的时候需要把大写的驼峰改为小写的字母同时在两个单词之前使用 - 链接(`<my-login></my-login>`);如果不使用驼峰,则直接拿名称来使用即可;
>
> 4、全局组件定义之后可以可以直接使用不需要import引入
>
> 5、定义组件的时候data为一个匿名函数其返回值为一个对象。在这个返回对象里面填写需要的数据。
>
> 6、定义全局组件的时候必须在vue初始化之前完成也就是必须放在new Vue()代码之前。
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
### 2、定义局部组件
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
通过components : { componentname:option} 的形式注册过一个子组件后才能在当前组件内使用该组件。
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
定义局部组件就是再vue实例中定义组件。
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
```js
// main.js
import Vue from 'vue'
// 子组件定义的时候如果提出组件的配置比如c那么也要放在new Vue之前。
let c = {
template: `
<div>
<div v-for="item in names">{{item.name}}</div>
</div>
`,
data: function() {
return {
names: [
{ name: 'good1' },
{ name: 'good2' },
{ name: 'good3' }
]
}
}
};
new Vue({
el: '#app',
data: {
username: 'daotin'
},
// 定义局部组件
components: {
goods:c
}
});
```
子组件的使用:
2018-09-04 20:53:38 +08:00
```html
<!DOCTYPE html>
<html lang="en">
<head>
2019-01-03 23:07:32 +08:00
<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>
2018-09-04 20:53:38 +08:00
</head>
<body>
2019-01-03 23:07:32 +08:00
<div id="app"></div>
<!--局部组件的使用-->
<goods></goods>
2018-09-04 20:53:38 +08:00
</body>
</html>
```
> 注意:
>
2019-01-04 10:13:06 +08:00
> 1、局部组件要使用的话**在哪个组件使用,就要在哪个组件中定义**,也叫注册。
2018-09-04 20:53:38 +08:00
>
2019-01-03 23:07:32 +08:00
> 2、定义子组件的时候如果把option提出来写也必须放在new Vue 之前。
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
## 三、动态组件
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
定义三个子组件`Home.js, Goods.js, Users.js`
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
> 一般组件的文件名首字母大写。
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
然后创建一个主组件App.js。在main.js里面这样做
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
在main.js里面定义局部组件App然后在main.js里面也可以定义template模板。
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
在main.js里面定义的模板会覆盖掉index.html里面的`<div id="app"></div>`
这里main.js里面的模板就是子组件App的内容实际上这样写的目的就是在主页显示App的内容所有其他组件比如Home.js等在App.js引入进行显示。
```js
new Vue({
el: '#app',
data: {},
template: '<App/>',
components: { App }
});
```
看看各个文件的内容:
```js
// Home.js
export let Home = {
template: `
<div>
<h2>首页</h2>
</div>
`
}
// Goods.js
export let Goods = {
template: `
<div>
<ul>
<li v-for="good in goods">{{good.goodsName}}</li>
</ul>
</div>
`,
data() {
return {
goods: [
{ goodsName: '苹果' },
{ goodsName: '香蕉' },
{ goodsName: '梨' },
]
2018-09-04 20:53:38 +08:00
}
2019-01-03 23:07:32 +08:00
},
}
//Users.js
export let Users = {
template: `
<div>
<ul>
<li v-for="user in users">{{user.name}}</li>
</ul>
</div>
`,
data() {
return {
users: [
{ name: 'lvonve' },
{ name: 'daotin' },
{ name: 'wenran' },
]
}
},
}
2018-09-04 20:53:38 +08:00
```
2019-01-03 23:07:32 +08:00
主组件App.js
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
```js
// 主组件
import { Home } from './pages/Home'
import { Goods } from './pages/Goods'
import { Users } from './pages/Users'
export let App = {
template: `
<div>
<a href="javascript:;" @click="goPage(nav.name)" v-for="nav in navs">{{nav.text}}</a>
<Component :is="currentPage" />
</div>
`,
data() {
return {
currentPage: 'Goods',
navs: [
{ text: '首页', name: 'Home' },
{ text: '商品列表', name: 'Goods' },
{ text: '用户列表', name: 'Users' }
]
}
},
methods: {
goPage(pagename) {
this.currentPage = pagename;
}
},
components: { Home, Goods, Users }
}
```
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
在主组件先import引入其他组件然后使用局部组件的方式进行定义之后使用`<Component is="Home" />` 类似于`<Home />` 由于这里需要动态显示所以is可变就成了`<Component :is="currentPage" />`
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
这样点击不同的a标签就会显示不同的内容默认显示Goods组件内容
2018-09-04 20:53:38 +08:00
2019-05-15 09:58:36 +08:00
![](./images/7.gif)
2018-09-04 20:53:38 +08:00
2019-01-04 10:13:06 +08:00
## 四、组件间传值
### 1、父组件向子组件传值
2018-09-04 20:53:38 +08:00
2019-01-03 23:07:32 +08:00
有这样一个需求我们的HomeGoodsUsers组件都需要有一个相同的头部组件Header但是需要Header里面的内容不同这就需要父组件HomeGoodsUsers传给子组件Header不同的值来显示不同的内容。
定义一个Header组件。
```js
// Header.js
export let Header = {
template: `
<div>
<h3>
我是Header组件
</h3>
</div>
`
}
```
父组件怎么给子组件传值呢?
使用`props`。
父组件传递的方式在子组件中自定义一个属性名称随意这里叫title属性值为Home
```js
template: `
<div>
<Header title="Home"/>
<h2>首页</h2>
</div>
`,
```
然后在子组件里面接收父组件传来的数据使用props接收来自父组件的数据接收属性为一个数组里面的值就是传过来的属性名。
既然是数组,就可以接收多个父组件传过来的数据。
```js
export let Header = {
// 使用props接收来自父组件的数据接收属性为一个数组里面的值就是传过来的属性名
props: ['title'],
template: `
<div>
<h3>我是Header组件</h3>
<p>我来自{{title}}</p>
</div>
`
}
2018-09-04 20:53:38 +08:00
```
2019-07-15 15:45:50 +08:00
*(added in 20190711)*
如果想要将一个对象的所有属性一次性全部传入,有一种快捷的方式传入整个对象的属性,而不需要将对象的属性一个个的传入:
![](https://raw.githubusercontent.com/Daotin/pic/master/img/20190711101320.png)
2019-01-04 10:13:06 +08:00
#### 1.1、props类型验证
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
我们上面props传递的都是字符串如果不小心传递的是数组啊对象啊什么的显示的时候也只会以字符串的显示显示如何对props传递的数据进行类型声明呢
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
我们需要在子组件里面进行props声明
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
```js
export let Header = {
props: {
obj: Object,
title: String
},
template: `
<div>
<h3>我是Header组件</h3>
<p>我来自{{title}}</p>
<p>{{obj}}</p>
</div>
`
}
```
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
父组件传值的时候和之前一样:
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
```js
import { Header } from '../components/Header'
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
export let Home = {
data() {
return {
title: 'Home',
obj: {
username: 'Daotin'
}
}
},
template: `
<div>
<Header :title="title" :obj="obj"/>
<h2>首页</h2>
</div>
`,
components: { Header }
}
```
2019-01-03 23:07:32 +08:00
2019-07-15 15:45:50 +08:00
*(added in 20190711)*
2021-05-17 14:10:39 +08:00
1、prop的类型验证可以是**多个类型**。
```js
props: {
handleData: {
type: [Array,Object],
default: () => {
}
},
}
```
2019-07-15 15:45:50 +08:00
2、prop的类型验证 type 还可以是一个自定义的构造函数。
![](https://raw.githubusercontent.com/Daotin/pic/master/img/20190711104244.png)
2019-01-04 10:13:06 +08:00
#### 1.2、props必填验证
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
还有个问题是当父组件不填写title值的时候子组件不显示但是不会报错。
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
**如何设置子组件声明的值为必填属性呢?**
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
子组件这样设置:
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
```js
export let Header = {
props: {
obj: Object,
title: {
type: String,
required: true
}
},
template: `
<div>
<h3>我是Header组件</h3>
<p>我来自{{title}}</p>
<p>{{obj}}</p>
</div>
`
}
```
父组件如果不写`title`属性的话,会报以下错误:
2019-05-15 09:58:36 +08:00
![](./images/27.png)
2019-01-04 10:13:06 +08:00
#### 1.3、props默认值
子组件:
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
```js
export let Header = {
props: {
obj: Object,
title: {
type: String,
default: 'Home'
// required: true
}
},
template: `
<div>
<h3>我是Header组件</h3>
<p>我来自{{title}}</p>
<p>{{obj}}</p>
</div>
`
}
```
当子组件设置默认属性的时候,就不需要必填属性了,因为有了默认值肯定填写了。
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
#### 1.4、props自定义匹配规则
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
如果父组件传递的是个数组,我们想限制数组的长度怎么办?上面的属性都不可以限制,这时候就需要自定义匹配规则了。
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
这里我们限制传递过来的数组长度在3-10之间长度。
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
子组件:
2019-01-03 23:07:32 +08:00
2019-01-04 10:13:06 +08:00
```js
export let Header = {
props: {
obj: Object,
title: {
type: String,
default: 'Default Home'
// required: true
},
arr: {
type: Array,
required: true,
validator: function(value) {
// value就是父组件传递过来的数组
if (value.length >= 3 && value.length <= 10) {
// 满足条件返回true
return true;
} else {
// 不满足条件返回false
return false;
}
}
}
},
template: `
<div>
<h3>我是Header组件</h3>
<p>我来自{{title}}</p>
<p>{{obj}}</p>
</div>
`
}
```
父组件这时传递个空数组:
```js
import { Header } from '../components/Header'
export let Home = {
data() {
return {
title: 'Home',
obj: {
username: 'Daotin'
},
arr: []
}
},
template: `
<div>
<Header :title="title" :obj="obj" :arr="arr"/>
<h2>首页</h2>
</div>
`,
components: { Header }
}
```
会报错如下,当我们传递的数组的长度满足的时候就不会报错了。
2019-05-15 09:58:36 +08:00
![](./images/28.png)
2019-01-04 10:13:06 +08:00
但是我觉得这个提示不明显,我们可以手动报错。
```js
export let Header = {
props: {
obj: Object,
title: {
type: String,
default: 'Default Home'
// required: true
},
arr: {
type: Array,
required: true,
validator: function(value) {
// value就是父组件传递过来的数组
if (value.length >= 3 && value.length <= 10) {
return true;
} else {
// return false;
throw new Error('数组的长度必须在3-10之间');
}
}
}
},
template: `
<div>
<h3>我是Header组件</h3>
<p>我来自{{title}}</p>
<p>{{obj}}</p>
</div>
`
}
```
2019-05-15 09:58:36 +08:00
![](./images/29.png)
2019-01-04 10:13:06 +08:00
现在报错的信息就很明显了。
2019-07-15 15:45:50 +08:00
> 注意:注意那些 prop 会在一个**组件实例创建之前**进行验证,所以实例的属性 (如 data、computed 等) 在 default 或 validator 函数中是不可用的。
*(added in 20190711)*
#### 1.5、prop 是单项数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,**每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。**这意味着你不应该在一个子组件内部改变 prop。如果你这样做了Vue 会在浏览器的控制台中发出警告。
![](https://raw.githubusercontent.com/Daotin/pic/master/img/20190711101755.png)
*(added in 20190711)*
### 非 Prop 的特性
一个非 prop 特性是指将一个属性传向一个子组件,但是该子组件并没有相应 prop 来接收。
也就是说子组件中没有使用props定义一个属性A但是如果你是父组件传入了子组件一个属性A那么属性A会自动加在**子组件的根元素**上。
最典型的是在子组件上写了一个`class=“A”`,那么子组件的根元素上就有了`class=“A”`
```js
<ChildComponent class="A" />
```
> 注意:
>
> 对于绝大多数特性来说,从外部提供给组件的值会替换掉组件内部设置好的值。所以如果传入 type="text" 就会替换掉 type="date" 并把它破坏!
>
> 庆幸的是class 和 style 特性会稍微智能一些即子组件内部的class值和父组件传入的class值两边的值会被合并起来从而得到最终的值。
如果你不想让父组件传入的属性出现在子组件根元素上,可以在子组件选项中加入:`inheritAttrs: false`
即:
```vue
Vue.component('my-component', {
inheritAttrs: false,
// ...
})
```
2019-01-04 10:13:06 +08:00
### 2、子组件向父组件传值
在此之前先介绍个插件`string-loader` 这个插件可以让我们组件的template属性的值提出到单独的html页面便于书写。
- 安装插件
```
npm i string-loader -D
```
- 配置config文件
```json
module: {
rules: [
{ test: /\.html$/, loader: 'string-loader' }
]
},
```
2019-01-03 23:07:32 +08:00
2019-01-04 23:45:20 +08:00
这里创建一个子组件A.js和A组件的template文件A.html。
2019-01-03 23:07:32 +08:00
2019-01-04 23:45:20 +08:00
A组件如何使用外部的template呢
2019-01-03 23:07:32 +08:00
2019-01-04 23:45:20 +08:00
```html
<!--A.html-->
<div>
<h4>
我说A组件。
<span>count = {{count}}</span>
<button @click="add">+</button>
</h4>
</div>
```
首先要导入A.html文件才能使用导入的方式和js一样。
```js
// A.js
import htmlA from './A.html'
export let A = {
template: htmlA
}
```
然后用相同的方式创建B.js和B.html文件。然后在父组件Home.js引入使用A组件和B组件。
现在的代码结构如下:
```js
// A.js
import htmlA from './A.html'
export let A = {
props: ['title'],
template: htmlA,
data() {
return {
count: 0
}
},
methods: {
add() {
this.count++;
}
}
}
// B.js
import htmlB from './B.html'
export let B = {
props: ['cCount'],
template: htmlB
}
// Home.js
import { Header } from '../components/Header'
import { A } from '../components/A'
import { B } from '../components/B'
export let Home = {
template: `
<div>
<Header :title="title"></Header>
<h1>首页</h1>
<A></A>
<B></B>
</div>
`,
data() {
return {
title: '首页的title',
count: 0
}
},
components: { Header, A, B }
}
```
A.html和B.html
```html
<!-- A.html -->
<div>
<h4>
我是A组件。
<span>count = {{count}}</span>
<button @click="add">+</button>
</h4>
</div>
<!-- B.html -->
<div>
<h4>
我是B组件。
<span>count = {{cCount}}</span>
</h4>
</div>
```
此时的需求是点击A组件的按钮A组件的count++然后将count值传给Home父组件。
子组件传值给父组件使用:`子组件.$emit(事件名,发送给父组件的数据)`
```js
// A.js
import htmlA from './A.html'
export let A = {
props: ['title'],
template: htmlA,
data() {
return {
count: 0
}
},
methods: {
add() {
this.count++;
}
},
// 监听数据的变化
watch: {
count() {
this.$emit('countChange', this.count);
}
}
}
```
我们在watch里面监听count值的变化然后将count值使用`this.$emit('countChange', this.count);`发送给父组件一旦count值发生变化就会触发countChange事件然后将this.count发送给父组件。
这时候在父组件监听这个countChange事件拿到子组件传过来的count
```js
import { Header } from '../components/Header'
import { A } from '../components/A'
import { B } from '../components/B'
export let Home = {
template: `
<div>
<Header :title="title"></Header>
<h1>首页</h1>
<A @countChange="change"></A>
<B></B>
</div>
`,
data() {
return {
title: '首页的title',
count: 0
}
},
methods: {
change(count) {
console.log(count); // 打印得到的子组件count值
this.count = count;
}
},
components: { Header, A, B }
}
```
2019-03-02 18:40:13 +08:00
如果想要change事件传多个值比如还想传个一个参数为123在第一个参数的位置那么模板的写法就要修改
2019-01-04 23:45:20 +08:00
2019-03-02 18:40:13 +08:00
`<A @countChange="change(123, $event)"></A>` ,第二个参数`$event` 就相当于子组件传来的数据。
2019-01-04 23:45:20 +08:00
其实之前不写参数的时候,就类似与`<A @countChange="change($event)"></A>`这种写法。
### 3、子组件之间相互传值
**方式一:中间人模式**
通过父组件间接传值通过props的方式传值。
> 子组件A --> 父组件 --> 子组件B
**方式二:中央事件总线**
创建新的vue实例做中间人传话分别调用emit()进行触发和on()进行监听。
创建一个`toolVue.js`文件:
```js
import Vue from 'vue'
export let media = new Vue();
```
toolVue文件只负责创建一个vue实例然后导出组件。
AB组件引入toolVue组件。然后A的count变化的时候触发countChange函数在B里面调用countChange函数得到A传递的数据。
```js
// A.js
import { media } from '../toolVue'
import htmlA from './A.html'
export let A = {
props: ['title'],
template: htmlA,
data() {
return {
count: 0
}
},
methods: {
add() {
this.count++;
}
},
watch: {
count() {
// 通过media组件当count变化时触发countChange并传入count值
media.$emit('countChange', this.count);
}
},
components: { media }
}
```
```js
// B.js
import { media } from '../toolVue'
import htmlB from './B.html'
export let B = {
// props: ['cCount'],
template: htmlB,
data() {
return {
cCount: 0
}
},
components: { media },
mounted() {
// 通过media调用countChange函数得到A的count值
// 注意这里面的this是media并不是B所以要用箭头函数
media.$on('countChange', count => {
this.cCount = count;
})
},
}
```
2019-01-03 23:07:32 +08:00
2019-01-04 23:45:20 +08:00
完成子组件A的count传递到子组件B。
2019-01-03 23:07:32 +08:00
2019-01-04 23:45:20 +08:00
> // 注意这里面的this是media并不是B所以要用箭头函数
> media.$on('countChange', count => {
> this.cCount = count;
> })
2019-01-03 23:07:32 +08:00
2019-05-15 09:58:36 +08:00
![](./images/8.gif)
2019-01-03 23:07:32 +08:00
2019-01-13 17:10:37 +08:00
#### 3.1、Tips
2019-01-03 23:07:32 +08:00
2019-01-13 17:10:37 +08:00
**需要注意的是:如果是同一个页面的两个组件传递数据的时候,可能会发生接收的组件先监听事件,而后发送组件才发送数据,这样接收组件就接收不到数据了。**
解决办法:**给发送组件加个延时。**
```js
// 发送组件
setTimeout(() => {
media.$emit('hasUser', this.user);
}, 50);
```
```js
// 接收组件
media.$on('hasUser', user => {
this.user = user;
});
```
2019-01-03 23:07:32 +08:00
2019-03-02 18:40:13 +08:00
---
2019-01-03 23:07:32 +08:00
2019-03-02 18:40:13 +08:00
(---------下面是旧笔记---------)
2019-01-03 23:07:32 +08:00
2018-09-04 20:53:38 +08:00
## 三、组件切换
我们在登录注册一个网站的时候,经常看到两个按钮,一个登录,一个注册,如果你没有账号的话,需要先注册才能登录。我们在点击登录和注册的时候,网页会相应的切换,登录页面就是登陆组件,注册页面就是注册组件,那么点击登录和注册,如何实现组件的切换呢?
### 1、方式一
**使用`flag`标识符结合`v-if`和`v-else`切换组件**
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
</head>
<body>
<div id="box">
<!-- 给a注册点击事件切换flag状态 -->
<a href="javascript:;" @click.prevent="flag=true">登录</a>
<a href="javascript:;" @click.prevent="flag=false">注册</a>
<!-- 使用v-if v-else切换组件 -->
<login v-if="flag">
</login>
<register v-else="flag">
</register>
</div>
<script>
Vue.component('login', {
template: '<h3>登录组件</h3>'
});
Vue.component('register', {
template: '<h3>注册组件</h3>'
});
var vm = new Vue({
el: "#box",
data: {
flag: true
},
methods: {}
});
</script>
</body>
</html>
```
**缺陷:**由于flag的值只有true和false所以只能用于两个组件间 的切换,当大于两个组件的切换就不行了。
### 2、方式二
**使用 component元素的`:is`属性来切换不同的子组件**
使用 `<component :is="componentId"></component>` 来指定要切换的组件。
componentId为需要显示的组件名称为一个字符串可以使用变量指定。
`componentId: 'login'` // 默认显示登录组件。
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
</head>
<body>
<div id="box">
<!-- 给a注册点击事件切换flag状态 -->
<a href="javascript:;" @click.prevent="componentId='login'">登录</a>
<a href="javascript:;" @click.prevent="componentId='register'">注册</a>
<component :is="componentId"></component>
</div>
<script>
Vue.component('login', {
template: '<h3>登录组件</h3>'
});
Vue.component('register', {
template: '<h3>注册组件</h3>'
});
var vm = new Vue({
el: "#box",
data: {
componentId: 'login' // 默认显示登录
},
methods: {}
});
</script>
</body>
</html>
```
**为组件切换添加过渡:**
很简单,只需要**用 transition 将 component 包裹起来**即可。
```html
<transition>
<component :is="componentId"></component>
</transition>
```
示例:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
<link rel="stylesheet" href="./lib/animate.css">
<style>
.loginDiv {
width: 200px;
height: 200px;
background-color: red;
}
.registerDiv {
width: 200px;
height: 200px;
background-color: blue;
}
</style>
</head>
<body>
<div id="box">
<!-- 给a注册点击事件切换flag状态 -->
<a href="javascript:;" @click.prevent="componentId='login'">登录</a>
<a href="javascript:;" @click.prevent="componentId='register'">注册</a>
<transition mode="out-in" enter-active-class="animated bounceInRight" leave-active-class="animated bounceOutRight">
<component :is="componentId"></component>
</transition>
</div>
<template id="login">
<div class="loginDiv">
</div>
</template>
<template id="register">
<div class="registerDiv">
</div>
</template>
<script>
Vue.component('login', {
template: '#login'
});
Vue.component('register', {
template: '#register'
});
var vm = new Vue({
el: "#box",
data: {
componentId: 'login'
},
methods: {}
});
</script>
</body>
</html>
```
> `mode="out-in"`:可以设置切换组件的模式为先退出再进入。
## 四、组件传值
### 1、父组件向子组件传值
我们先通过一个例子看看子组件可不可以直接访问父组件的数据:
```html
<body>
<div id="box">
<mycom></mycom>
</div>
<template id="temp">
<h3>子组件 --- {{msg}}</h3>
</template>
<script>
var vm = new Vue({
el: "#box",
data: {
msg: '父组件的msg'
},
methods: {},
components: {
mycom: {
template: '#temp'
}
}
});
</script>
</body>
```
由于 components 定义的是私有组件我们直接在子组件中调用父组件的msg会报错。
2019-05-15 09:58:36 +08:00
![](./images/15.png)
2018-09-04 20:53:38 +08:00
那么,怎么让子组件使用父组件的数据呢?
**父组件可以在引用子组件的时候, 通过 属性绑定v-bind: 的形式, 把需要传递给子组件的数据,以属性绑定的形式,传递到子组件内部,供子组件使用 。**
```html
<body>
<div id="box">
<mycom v-bind:parentmsg="msg"></mycom>
</div>
<template id="temp">
<h3>子组件 --- 父组件:{{parentmsg}}</h3>
</template>
<script>
var vm = new Vue({
el: "#box",
data: {
msg: '父组件的msg'
},
methods: {},
components: {
mycom: {
template: "#temp",
// 对传递给子组件的数据进行声明,子组件才能使用
props: ['parentmsg']
}
}
});
</script>
</body>
```
注意:父组件绑定的**属性名称不能有大写字母**,否则不会显示,并且在命令行会有提示:
2019-05-15 09:58:36 +08:00
![](./images/16.png)
2018-09-04 20:53:38 +08:00
**组件data数据和props数据的区别**
- data数据是子组件私有的可读可写
- props数据是父组件传递给子组件的只能读不能写。
**案例:发表评论功能**
父组件为评论列表子组件为ID评论者内容和按钮的集合在输入ID评论者等内容然后点击添加的时候需要首先获取子组件的list列表然后再添加新的列表项到列表中。
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
</head>
<body>
<div id="box">
<mycom :plist="list"></mycom>
<ul>
<li v-for="item in list" :key="item.id">
ID:{{item.id}} --- 内容:{{item.content}} --- 评论人:{{item.user}}
</li>
</ul>
</div>
<template id="tmp1">
<div>
<label>
ID:
<input type="text" v-model="id">
</label>
<br>
<label>
评论者:
<input type="text" v-model="user">
</label>
<br>
<label>
内容:
<textarea v-model="content"></textarea>
</label>
<br>
<!-- 把父组件的数据作为子组件的函数参数传入 -->
<input type="button" value="添加评论" @click="addContent(plist)">
</div>
</template>
<script>
var vm = new Vue({
el: "#box",
data: {
list: [{
id: Date.now(),
user: 'user1',
content: 'what'
}, {
id: Date.now(),
user: 'user2',
content: 'are'
}]
},
methods: {},
components: {
mycom: {
template: '#tmp1',
data: function () {
return {
id: '',
user: '',
content: '',
}
},
methods: {
addContent(plist) {
plist.unshift({
id: this.id,
user: this.user,
content: this.content
});
}
},
props: ['plist']
}
}
});
</script>
</body>
</html>
```
把添加ID评论人内容作为子组件把列表作为父组件然后把添加的数据放到父组件列表上由于要获取到父组件列表的数据所以必然涉及到父组件向子组件传值的过程。这里还通过子组件方法参数来保存父组件的数据到子组件的数据中。
### 2、父组件向子组件传方法
既然父组件可以向子组件传递数据,那么也可以向子组件传递方法。
```html
<body>
<div id="box">
<mycom v-bind:parentmsg="msg" @parentfunc="show"></mycom>
</div>
<template id="temp">
<div>
<input type="button" value="调用父组件方法" @click="sonClick">
<h3>子组件 --- 父组件:{{parentmsg}}</h3>
</div>
</template>
<script>
var vm = new Vue({
el: "#box",
data: {
msg: '父组件的msg'
},
methods: {
show(data1, data2) {
console.log("这是父组件的show方法" + data1 + data2);
}
},
components: {
mycom: {
template: "#temp",
// 对传递给子组件的数据进行声明,子组件才能使用
props: ['parentmsg'],
methods: {
sonClick() {
// 调用父组件的show方法
this.$emit("parentfunc", 111, 222);
}
}
}
}
});
</script>
</body>
```
> 1、`@parentfunc="show"` 绑定父组件的show方法。
>
> 2、`<input type="button" value="调用父组件方法" @click="sonClick">` 点击按钮调用父组件的show方法
>
> 3、在 子组件的 sonClick 方法中使用 `this.$emit("parentfunc");` 来调用父组件的show方法
>
> 4、父组件的show方法也可以传参在调用的时候实参从 this.$emit 的第二个参数开始传入。
>
> **5、如果 this.$emit 的第二个参数传的是子组件的data数据那么父组件的方法就可以获得子组件的数据这也是把子组件的数据传递给父组件的方式。**
### 3、使用 ref 获取DOM和组件的引用
我们知道Vue不推荐直接获取DOM元素那么在Vue里面怎么获取DOM及组件元素呢
我们呢可以在元素上使用 `ref` 属性来获取元素。
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
</head>
<body>
<div id="box">
<input type="button" value="获取元素" @click="getrefs" ref="mybtn">
<h3 ref="myh3">这是H3</h3>
<mycom ref="mycom"></mycom>
</div>
<template id="tmp1">
</template>
<script>
// 定义组件
Vue.component('mycom', {
template: '#tmp1',
data: function () {
return {
msg: '子组件的msg',
pmsg: ''
}
},
methods: {
show(data) {
console.log('调用子组件的show');
this.pmsg = data;
console.log(this.pmsg);
},
}
});
var vm = new Vue({
el: "#box",
data: {
parentmsg: '父组件的msg'
},
methods: {
getrefs() {
console.log(this.$refs.myh3);
console.log(this.$refs.mycom.msg);
this.$refs.mycom.show(this.parentmsg);
}
}
});
</script>
</body>
</html>
```
2019-05-15 09:58:36 +08:00
![](./images/17.png)
2018-09-04 20:53:38 +08:00
**总结:**
1、ref 属性不仅可以获取DOM元素也可以获取组件无论全局还是私有组件元素。
2、获取到组件元素后就可以获取组件元素的data数据和methods方法。
**3、获取到组件中的方法后可以传入VM的data数据就可以把VM的data数据传入组件中。**
2018-11-19 19:22:56 +08:00