Vue组件
-
组件:由html、css、js三部分组成的独立单位,可以类似于变量,重复使用
-
组件其实就是vue实例(对象),一个组件就是一个vue实例(对象)
-
new Vue() 产生的也是实例(对象),所以也是组件,称之为根组件
一个页面建议只出现一个根组件(项目开发模式下,一个项目建议只出现一个根组件)
-
组件的html页面结构由 template 实例成员提供
template提供的html结构是用来构建虚拟DOM,真实DOM最终会被虚拟DOM替换
根组件一般不通过template,就由挂载点来提供构建虚拟DOM的页面结构,根组件如果提供了template,还需要设置挂载点作为替换占位。
template模板有且只有一个根标签。
根组件
通过new Vue()创建的实例就是根组件(实例与组件一一对应,一个实例就是一个组件)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>根组件</title>
</head>
<body>
<div id="app">
<h1>{{ msg }}</h1>
</div>
<script src="js/vue.js"></script>
<script>
// 通过new Vue()创建的实例就是根组件(实例与组件一一对应,一个实例就是一个组件)
// 每个组件均拥有模板,template
let app = new Vue({
// 根组件的模板就是挂载点
el: '#app',
data: {
msg: '根组件'
},
// 模板:由""包裹的html代码块,出现在组件的内部,赋值给组件的$template变量
// 显示书写模板,就会替换挂载点,但根组件必须拥有挂载点
template: "<div>显示模板</div>"
});
// app.$template
</script>
</body>
</html>
子组件
在根组件template中加载的组件,称之为根组件的子组件
如何定义子组件
组件就是一个普通对象,内部采用vue语法结构,被vue注册解释后,就会成为vue组件。eg:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>子组件</title>
</head>
<body>
<!--根组件的template-->
<div id="app">
<!--在根组件template中加载的组件,称之为根组件的子组件-->
<my-tag></my-tag>
<my-tag></my-tag>
<my-tag></my-tag>
<tag></tag>
</div>
</body>
<script src="js/vue.js"></script>
<script>
// 1、定义组件
// 2、注册组件
// 3、使用组件
// 如何定义子组件:组件就是一个普通对象,内部采用vue语法结构,被vue注册解释后,就会成为vue组件
let myTag = {
template: `
<div>
<h3>子组件</h3>
<p>我是自定义的子组件</p>
</div>
`,
};
// 了解:全局组件,不要注册就可以直接使用
Vue.component('tag', {
template: `
<div>
<h3>全局组件</h3>
<p>我是自定义的全局组件</p>
</div>
`,
});
new Vue({
el: '#app',
components: {
// 'my-tag': myTag,
// myTag: myTag,
myTag,
}
})
</script>
</html>
子组件数据局部化
// 子组件
let tag = {
template: `...`,
// 能被复用的组件(除了根组件),数据都要做局部化处理,因为复用组件后,组件的数据是相互独立的
// data的值为绑定的方法的返回值,返回值是存放数据的字典
data() {
return {
// 数据...
}
}
}
// 在哪个组件模板中出现的属性变量和方法变量,都由当前所属组件自己提供
eg:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title></title>
<style>
.wrap {
calc(200px * 4 + 80px);
margin: 0 auto;
user-select: none;
}
.box {
200px;
height: 260px;
/*border: 1px solid black;*/
background-color: rgba(10, 200, 30, 0.5);
border-radius: 10px;
float: left;
margin: 10px;
}
.box img {
100%;
/*height: 200px;*/
border-radius: 50%;
}
.box p {
text-align: center;
}
</style>
</head>
<body>
<div id="app">
<div class="wrap">
<tag></tag>
<tag></tag>
<tag></tag>
<tag></tag>
</div>
</div>
</body>
<script src="js/vue.js"></script>
<script>
let titleTag = {
template: `
<p>
<b>
这是一种纯二哈
</b>
</p>
`,
};
let tag = {
template: `
<div class="box">
<img src="img/001.jpg" alt="">
<title-tag />
<p @click="fn">
锤它:<b>{{ num }}下</b>
</p>
</div>
`,
// 能被复用的组件(除了根组件),数据都要做局部化处理,因为复用组件后,组件的数据是相互独立的
// data的值为绑定的方法的返回值,返回值是存放数据的字典
data () {
return {
num: 0
}
},
methods: {
fn() {
this.num ++
}
},
components: {
titleTag,
}
};
new Vue({
el: '#app',
components: {
tag,
}
});
</script>
</html>
父组件传递数据给 子组件
通过绑定属性的方式进行数据传递
- 数据在父组件中产生
- 在父组件中渲染子组件,子组件绑定自定义属性,附上父组件中的数据
- 子组件自定义属性在子组件的props成员中进行声明(采用字符串反射机制)
- 在子组件内部,就可以用props声明的属性(直接作为变量)来使用父组件中的数据
<div id="app">
<!-- 在父组件汇总使用子组件-->
<tag :sub_msg="msg" />
</div>
<script>
// 子组件
let tag = {
// 在组件内部就可以通过设置的自定义属性sub_msg,拿到外部选择子组件提供给属性的值
props: ['sub_msg'],
template: `<div>{{ sub_msg }}</div>`,
}
new Vue({
el: '#app',
// 注册子组件
components: {
tag,
},
data: {
msg: '父级数据'
}
})
</script>
eg:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>父传子</title>
<style>
.wrap {
calc(200px * 4 + 80px);
margin: 0 auto;
user-select: none;
}
.box {
200px;
height: 260px;
/*border: 1px solid black;*/
background-color: rgba(10, 200, 30, 0.5);
border-radius: 10px;
float: left;
margin: 10px;
}
.box img {
/* 100%;*/
height: 160px;
border-radius: 50%;
margin: 0 auto;
display: block;
}
.box p {
text-align: center;
}
</style>
</head>
<body>
<div id="app">
<div class="wrap">
<tag v-for="dog in dogs" v-bind:dog="dog" :a="1" :b="2" />
</div>
</div>
</body>
<script src="js/vue.js"></script>
<script>
let dogs = [
{ title: '二哈1号', img: 'img/1.jpg', },
{ title: '二哈2号', img: 'img/2.jpg', },
{ title: '二哈3号', img: 'img/3.jpg', },
{ title: '二哈4号', img: 'img/4.jpg', },
];
let tag = {
// 在组件内部就可以通过设置的自定义属性,拿到外部选择子组件提供给属性的值
props: ['dog', 'a', 'b', 'z'],
template: `
<div class="box">
<img :src="dog.img" alt="">
<p>
<b>
{{ dog.title }}
</b>
</p>
<p @click="fn">
锤它:<b>{{ num }}下</b>
</p>
</div>
`,
data () {
return {
num: 0,
}
},
methods: {
fn() {
this.num ++
}
},
};
new Vue({
el: '#app',
data: {
dogs,
},
components: {
tag,
}
});
</script>
</html>
子组件传递数据给父组件
通过发送事件请求的方式进行数据传递
<div id="app">
<h1> {{ title }} </h1>
<!-- 组件标签不能添加系统事件,只能添加自定义事件,自定义事件在组件内部通过$emit主动触发 -->
<tag @self_action="changeTitle"/>
</div>
<script>
let tag = {
template: `
<div>
<input v-model="sub_title" />
</div>
`,
data() {
return {
sub_title: ''
}
},
watch: {
// 监听sub_title属性,值一改变就会触发
sub_title() {
// 将sub_title与父级的title建立关联
// 激活(触发)self_action自定义事件
this.$emit('self_action', this.sub_title)
}
}
};
new Vue({
el: '#app',
components: {
tag,
},
data: {
title: '父级初始标题'
},
methods: {
changeTitle(sub_title) {
this.title = sub_title ? sub_title : '父级初始标题';
}
}
})
</script>
eg:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>子传父</title>
<style>
ul {
list-style: none;
}
.d-btn {
font-size: 12px;
15px;
display: inline-block;
}
.d-btn:hover {
color: red;
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<input type="text" v-model="msg">
<button @click="send_comment">留言</button>
<ul>
<tag v-for="(v, i) in comments" :msg="v" :index="i" @f1="deleteMsg"/>
</ul>
</div>
</body>
<script src="js/vue.js"></script>
<script>
let tag = {
props: ['msg', 'index'],
template: `
<li>
<i class="d-btn" @click="fn">x</i>
<b>{{ msg }}</b>
</li>
`,
methods: {
fn () {
// 点击子集,要告诉父级删除第几条数据,因为comments在父级中
// 需要通知父级,f1是事件,事件才能绑定方法,绑定父级的方法deleteMsg,才能接收到传过去的数据
this.$emit('f1', this.index);
}
}
};
new Vue({
el: '#app',
data: {
msg: '',
comments: localStorage.comments ? JSON.parse(localStorage.comments) : [],
},
components: {
tag,
},
methods: {
send_comment() {
if (this.msg) {
this.comments.push(this.msg);
this.msg = '';
localStorage.comments = JSON.stringify(this.comments);
}
},
deleteMsg(index) {
this.comments.splice(index, 1);
localStorage.comments = JSON.stringify(this.comments);
}
}
})
</script>
<script>
// localStorage,sessionStorage不能直接存储数组和对象,需要序列化为json
localStorage.arr = JSON.stringify([1, 2, 3]); // 前端操作json序列化
let res = JSON.parse(localStorage.arr); // 前端解析json数据
console.log(res, res[2]);
</script>
</html>
综合应用练习
有以下广告数据(实际数据命名可以略做调整)
ad_data = {
tv: [
{img: 'img/tv/001.png', title: 'tv1'},
{img: 'img/tv/002.png', title: 'tv2'},
{img: 'img/tv/003.png', title: 'tv3'},
{img: 'img/tv/004.png', title: 'tv4'},
],
phone: [
{img: 'img/phone/001.png', title: 'phone1'},
{img: 'img/phone/002.png', title: 'phone2'},
{img: 'img/phone/003.png', title: 'phone3'},
{img: 'img/phone/004.png', title: 'phone4'},
]
}
i) 有两个大标题,电视和手机,点击对应的标题,渲染对应的数据
ii) 一个字典作为一个显示单位,定义一个子组件进行渲染(涉及父子组件传参)
在上一题基础上,页面最下方有一个 h2 标签,用来渲染用户当前选择的广告(点击哪个广告就是选中哪个广告)
i)当没有点击任何广告,h2 标签显示:未选中任何广告
ii)当点击其中一个广告,如tv1,h2 标签显示:tv1被选中
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title></title>
<style>
.wrap {
calc(200px * 4 + 80px);
margin: 0 auto;
user-select: none;
}
.box {
200px;
height: 260px;
/*border: 1px solid black;*/
background-color: rgba(10, 200, 30, 0.5);
border-radius: 10px;
float: left;
margin: 10px;
overflow: hidden;
}
.box img {
/* 100%;*/
height: 160px;
/*border-radius: 50%;*/
margin: 0 auto;
display: block;
}
.box p {
text-align: center;
}
.action{
background-color: pink;
}
</style>
</head>
<body>
<div id="app">
<div class="wrap">
<p>
<button :class="{action: role === 'tv'}" @click="show('tv')">点击展示电视</button>
<button :class="{action: role === 'phone'}" @click="show('phone')">点击展示手机</button>
</p>
<div v-if="role === 'tv'">
<tag v-for="(tv, i) in tv" :data="tv" :index="i" @f1="choice"></tag>
</div>
<div v-else-if="role === 'phone'">
<tag v-for="(phone, i) in phone" :data="phone" :index="i" @f1="choice"></tag>
</div>
</div>
<div>
<h2>{{ msg }}</h2>
</div>
</div>
</body>
<script src="js/vue.js"></script>
<script>
let tv = [
{img: 'img/tv/001.jpg', title: 'tv1'},
{img: 'img/tv/002.jpg', title: 'tv2'},
{img: 'img/tv/003.jpg', title: 'tv3'},
{img: 'img/tv/004.jpg', title: 'tv4'},
];
let phone = [
{img: 'img/phone/001.jpg', title: 'phone1'},
{img: 'img/phone/002.jpg', title: 'phone2'},
{img: 'img/phone/003.jpg', title: 'phone3'},
{img: 'img/phone/004.jpg', title: 'phone4'},
];
let tag = {
props: ['data', 'index'],
template: `
<div class="box" @click="fn">
<p>
<b>{{ data.title }}</b>
</p>
<img :src="data.img" alt="">
</div>
`,
methods: {
fn(){
this.$emit('f1', this.index);
}
}
};
new Vue({
el: '#app',
data: {
tv,
phone,
role: 'tv',
msg: '未选中任何广告',
},
components: {
tag,
},
methods: {
show(role) {
this.role = role;
},
choice(index) {
// console.log(111);
let obj = this.role==='tv'? this.tv : this.phone;
this.msg = obj ? obj[index]['title'] +'被选中' : this.msg;
},
}
});
</script>
</html>