从“心”认识Vue(五):父组件与子组件
前言
学习vue的时候学完模板语法,上来就开始了脚手架,虽然上手快了点,但是感觉还是少了一点衔接,知识遇到了断层,于是自己就再补了一点,理解起来比较顺畅一些。
- 上篇文章:从“心”认识Vue(四):组件的基本使用
一、父组件与子组件的关系
我们将某段代码封装成一个组件,而这个组件又在另一个组件中引入,而引入该封装的组件的文件叫做父组件,被引入的组件叫做子组件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 4.使用父组件2-->
<comp2></comp2>
</div>
<script src="node_modules/vue/dist/vue.min.js"></script>
<script>
//1.注册组件构造器对象
const comp1 = Vue.extend({
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容,哈哈哈哈</p>
</div>
`
})
const comp2 = Vue.extend({
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容,嘻嘻嘻嘻</p>
<comp1></comp1>
</div>
`,
//3.注册子组件1,并在comp2里面使用组件1
components: {
comp1:comp1
}
})
new Vue({
el: "#app",
data: {
msg: "haha"
},
//2.注册父组件2
components:{
comp2:comp2
}
})
</script>
</body>
</html>
二、注册组件语法糖
语法糖,也称糖衣语法,指的是计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。即在功能实现上与原代码一模一样,但是用法很简洁方便,提高程序员工作效率。
例子1中的注册组件比较管繁琐,vue提供了注册组件的语法糖,即省略了注册组件的步骤,直接用一个对象来代替:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<comp1></comp1>
<comp2></comp2>
</div>
<script src="node_modules/vue/dist/vue.min.js"></script>
<script>
//1.全局组件注册的语法糖
// const comp1 = Vue.extend()
//2。注册组件
Vue.component("comp1",{
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容,哈哈哈哈</p>
</div>
`
})
new Vue({
el: "#app",
data: {
msg: "haha"
},
//局部组件语法糖
components: {
"comp2": {
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容,xixix</p>
</div>
`
}
}
})
</script>
</body>
</html>
三、模板与组件的分离写法
将模板与组件分离开来写,将使代码变得更加清晰。
vue提供了两种方案定义html内容:
- 一是使用script标签
- 二是使用templete标签
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<comp1></comp1>
</div>
<!-- 1.script标签写法,注意类型必须是text/x-template-->
<!--<script type="text/x-template" id="comp1">-->
<!-- <div>-->
<!-- <h2>我是标题1</h2>-->
<!-- <p>我是内容,哈哈哈哈</p>-->
<!-- </div>-->
<!--</script>-->
<!--2.template标签-->
<template id="comp1">
<div>
<h2>我是标题1</h2>
<p>我是内容,哈哈哈哈</p>
</div>
</template>
<script src="node_modules/vue/dist/vue.min.js"></script>
<script>
//1。注册一个全局组件
Vue.component("comp1", {
template: "#comp1"
})
new Vue({
el: "#app",
data: {
msg: "haha"
}
})
</script>
</body>
</html>
四、组件可以访问vue实例数据吗?
先测试一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<comp></comp>
</div>
<template id="comp1">
<div>
<h2>{{title}}</h2>
<p>我是内容,哈哈哈哈</p>
</div>
</template>
<script src="node_modules/vue/dist/vue.min.js"></script>
<script>
//1.注册全局组件
Vue.component("comp",{
template:"#comp1",
})
new Vue({
el: "#app",
data: {
msg: "haha",
title: "我是标题"
}
})
</script>
</body>
</html>
结果:vue报错,title未定义,那么说明组件不可以访问vue实例数据。
- 组件是一个单独功能模块的封装,有自己的html模板,也应该有自己的数据data;
- 组件里也有一个自己的data属性
- 组件的data是一个函数, 而且这个函数返回一个对象,对象内保存着数据
五、为什么组件的data必须是一个函数?
- 首先来看如果不是一个函数会是怎样,按照vue实例里的写法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 创建组件实例-->
<comp></comp>
<comp></comp>
<comp></comp>
</div>
<template id="comp">
<div>
<h3>当前计数:{{count}}</h3>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="node_modules/vue/dist/vue.min.js"></script>
<script>
//1.注册全局组件
Vue.component("comp",{
template:"#comp",
data:{
count:0
},//报错,如果这样不同组件会共享同一个数据,造成相互影响
methods:{
increment(){
this.count++
},
decrement(){
this.count--
}
}
})
new Vue({
el: "#app",
data: {
msg: "haha"
}
})
</script>
</body>
</html>
结果:报错.
- 我们再给它传入一个对象试试:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 创建组件实例-->
<comp></comp>
<comp></comp>
<comp></comp>
</div>
<template id="comp">
<div>
<h3>当前计数:{{count}}</h3>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="node_modules/vue/dist/vue.min.js"></script>
<script>
const obj1 = {
count:0
}
//1.注册全局组件
Vue.component("comp",{
template:"#comp",
// data:{
// count:0
// },//报错,如果这样不同组件会共享同一个数据,造成相互影响
data(){
return obj1;//不同组件获取到同一个对象,数据相互影响
},
methods:{
increment(){
this.count++
},
decrement(){
this.count--
}
}
})
new Vue({
el: "#app",
data: {
msg: "haha"
}
})
</script>
</body>
</html>
再来看一下结果:
结果发现三个组件之间的数据被相互影响了,这是我们不愿意看到的。因为我们需要组件之间数据相互独立。
接着我们将它改为正确的写法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 创建组件实例-->
<comp></comp>
<comp></comp>
<comp></comp>
</div>
<template id="comp">
<div>
<h3>当前计数:{{count}}</h3>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="node_modules/vue/dist/vue.min.js"></script>
<script>
//const obj1 = {
// count:0
//}
//1.注册全局组件
Vue.component("comp",{
template:"#comp",
//data数据相互独立
data(){
return {
count:0
}
},
// data数据相互影响
// data:{
// count:0
// },//报错,如果这样不同组件会共享同一个数据,造成相互影响
// data(){
// return obj1;//不同组件获取到同一个对象,数据相互影响
// },
methods:{
increment(){
this.count++
},
decrement(){
this.count--
}
}
})
new Vue({
el: "#app",
data: {
msg: "haha"
}
})
</script>
</body>
</html>
结果正确了:
到这里我们可以得出结论:
- 1.组件data如果不是一个函数,vue会报错;
- 2.组件data需要每个组件之间都保持数据的独立,每个组件调用data时都会返回一个新的对象,这就确保了组件之间数据不会相互影响。
六、父子之间通信
1.父传子
步骤:
- 1.在父级添加上数据
- 2.在子级添加props声明需要传递的数据变量
- 3.在组件实例中传递数据
- 4.在子组件中使用数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 2.carr是子组件的属性,接收到来自父级的数据-->
<comp :carr="arr" :cmsg="msg"></comp>
<!-- <comp :carr="arr" ></comp> 当cmsg为required时这里会报错-->
</div>
<template id="comp">
<div>
<ul>
<!-- 3.在子组件内可以使用到来自父级的数据-->
<li v-for="(v,i) in carr">{{v}}</li>
</ul>
{{cmsg}}
</div>
</template>
<!-- 使用生产版本一些错误会被忽略,而开发版本则不会-->
<script src="node_modules/vue/dist/vue.js"></script>
<script>
//子组件
const comp = {
template: "#comp",
//1.props声明需要从父级接收到的数据
// props:["carr","cmsg"]
//1.props验证
props: {
// 1.1限制类型
// carr:Array,
// cmsg:String
// 1.2提供默认值
cmsg: {
type: String,
default: "abcabc",
required: true //该属性必须传值
},
carr: {
type: Array,
//在高版本中数组默认值必须是一个函数
default() {
return []
}
}
}
}
//root组件
new Vue({
el: "#app",
data: {
//0.父级里的数据
msg: "haha",
arr: ["xixi", "haha", "heiehie"]
},
components: {
comp
}
})
// 总结步骤:
// 1.在父级添加上数据
// 2.在子级添加props声明需要传递的数据变量
// 3.在组件实例中传递数据
// 4.在子组件中使用数据
</script>
</body>
</html>
2.子传父
步骤:
- 1.子组件里添加数据
- 2.子组件模板添加点击事件
- 3.添加点击事件方法,使用$emit进行数据传递,第一个参数是自定义事件名称,第二个参数是需要传递的数据
- 4.在父组件模板里添加自定义事件,添加方法,注意方法名称后面不加括号,vue默认传递数据
- 5.父组件里添加自定义事件处理函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--父组件模板-->
<div id="app">
<!-- 4.在父组件模板里添加自定义事件,添加方法,注意方法名称后面不加括号-->
<comp @item-click="comClick"></comp>
<h2>你点击的是:{{item.name}}</h2>
</div>
<!--子组件模板-->
<template id="comp">
<div>
<!-- 2.子组件模板添加点击事件-->
<button v-for="items in categories" @click="btnClick(items)">{{items.name}}</button>
</div>
</template>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
//子组件
const comp = {
template: "#comp",
//1.子组件里添加数据
data() {
return {
categories:[
{id:"1",name:"推荐"},
{id:"2",name:"手机"},
{id:"3",name:"电脑"},
{id:"4",name:"箱包"},
]
}
},
methods:{
btnClick(item){
//3.添加点击事件方法,使用$emit进行数据传递,第一个参数是自定义事件名称,第二个参数是需要传递的数据
this.$emit("item-click",item)
}
}
}
//父组件
new Vue({
el: "#app",
data: {
msg: "haha",
item:""
},
components: {
comp
},
methods:{
// 5.父组件里添加自定义事件处理函数
comClick(item){
console.log(item)
this.item = item
}
}
})
// 总结:
// 1.子组件里添加数据
// 2.子组件模板添加点击事件
// 3.添加点击事件方法,使用$emit进行数据传递,第一个参数是自定义事件名称,第二个参数是需要传递的数据
// 4.在父组件模板里添加自定义事件,添加方法,注意方法名称后面不加括号,vue默认传递数据
// 5.父组件里添加自定义事件处理函数
</script>
</body>
</html>
七、父访问子/子访问父
有时我们不想传递数据了,直接拿到父或子级的数据:(不常用)
- 父访问子:$children 或者 $refs方法
- 子访问父:$parent方法
1.父访问子
一、ref方法
- 1.给组件添加ref属性xxx
- 2.父级方法调用this.$refs.xxx.子级数据
二、children方法
- 1.父级方法中直接通过children索引取值,this.$children[索引].子级数据
2.子访问父:
- 1.父级定义数据
- 2.子级方法里直接调用this.$parent.父级数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 2.添加ref属性-->
<comp ref="xixi"></comp>
<button @click="btnClick()">点击访问子组件</button>
<h2>拿到子组件的数据是:{{sonName}}</h2>
</div>
<template id="comp">
<div>
{{name}}
<br>
<button @click="showMsg()">点击访问父组件</button>
<h2>拿到父组件的数据是:{{parentData}}</h2>
</div>
</template>
<script src="node_modules/vue/dist/vue.min.js"></script>
<script>
const comp = {
template:"#comp",
data(){
return {
name:"我是子组件的name",
parentData:""
}
},
methods:{
showMsg(){
this.parentData = this.$parent.parentMsg
}
}
}
new Vue({
el: "#app",
data: {
parentMsg: "我是父组件的haha",
sonName:""
},
components: {
comp
},
methods:{
btnClick(){
// 1.$children 用的少 因为不容易确定组件的索引值
// console.log(this.$children[0].name);
// 2.$refs 常用 对象类型,默认为空,必须在组件上添加ref属性后生效
console.log(this.$refs.xixi.name)
this.sonName = this.$refs.xixi.name
}
}
})
</script>
</body>
</html>
八、非父子通信(以后再讲)
1.中央事件总线
2.vuex状态管理
总结
- 1.父组件与子组件的关系:将某段代码封装成一个组件,而这个组件又在另一个组件中引入,而引入该封装的组件的文件叫做父组件,被引入的组件叫做子组件。
- 2.注册组件语法糖:将注册组件的步骤用一个对象代替;
- 3.vue提供两种方法分享模板与组件:script标签与template标签
- 4.组件不能访问vue实例数据,组件有自己存放数据的地方;
- 5.组件data必须要是一个函数,不然vue会报错,而且为了不同组件之间必须保证数据的独立,每个组件的data函数要返回一个新的对象。
- 6.父传子用props
- 1)在父级添加上数据
- 2)在子级添加props声明需要传递的数据变量
- 3)在组件实例中传递数据
- 4)在子组件中使用数据
- 7.子传父用自定义事件$emit
- 1)子组件里添加数据
- 2)子组件模板添加点击事件
- 3)添加点击事件方法,使用$emit进行数据传递,第一个参数是自定义事件名称,第二个参数是需要传递的数据
- 4)在父组件模板里添加自定义事件,添加方法,注意方法名称后面不加括号,vue默认传递数据
- 5)父组件里添加自定义事件处理函数
- 8.父访问子数据用$children或者 $refs, $children不常用 ,因为常常不能准确知道索引值
- 9.子访问父数据用$parent,而这两种访问方式都不建议使用,因为代码耦合度太高,不利于代码的利用。