1.全局组件与局部组件
组件的意义:写一个可以复用的小模块,
举例:写一段可以复用的标签
使用步骤1创建组件对象2注册组件(分为全局和局部)3使用组件
写法代码:
<body>
<div id="app">
<!--3.使用组件-->
<cpn></cpn>
</div>
<div id="app2">
<cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpn = Vue.extend({
template:
`
<div>
<h2>模板</h2>
<h2>模板</h2>
</div>
`
})
// 2.全局注册组件
// Vue.component('cpn', cpn);
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
}
})
//局部组件的使用,此时id=app的div里不可使用此组件
const aaa2 = new Vue({
el: "#app2",
components :{
cpn: cpn
}
})
</script>
</body>
效果:
一个浏览器读出来了标签一个没读出来,因为没有注册
2.父组件与子组件
介绍:如果一个组件能调用另一个组件,那么这个组件是父组件,被调用的是子组件
代码:
<body>
<div id="app">
<cpn1></cpn1>
<cpn2></cpn2>
</div>
<script src="../js/vue.js"></script>
<script>
//创造子组件
const cpn2 = Vue.extend({
template:
`
<div>
<h2>模板2</h2>
</div>
`
})
//创造父组件
const cpn1 = Vue.extend({
template:
`
<div>
<h2>模板1</h2>
<cpn2></cpn2>
</div>
`,
components:{
cpn2: cpn2
}
})
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
},
components: {
cpn1: cpn1
}
})
</script>
</body>
效果:
不难看出模板2被模板1调用,所以模板2是子组件,模板1是父组件
值得一提的是cpn2(模板2)并不能直接被id=app的div调用,因为他是在模板1生成的时候注册的,模板1生成时可以调用,但是模板1注册的时候(在new vue)不会顺带模板2,除非模板2也在new vue里注册
3.组件构造的语法糖
介绍:此语法糖就是把构造组件放在注册组件里面,请看写法:
<script>
//1.非语法糖
const cpn1 = Vue.extend({
template:
`
<div>
<h2>模板1</h2>
</div>
`,
})
Vue.component('cpn', cpn1);//全局注册
//局部注册
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
},
components: {
cpn1: cpn1
}
})
//2.语法糖
//全局注册
Vue.component('cpn', {
template:
`
<div>
<h2>模板1</h2>
</div>
`,
});
//局部注册
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
},
components: {
cpn1: {
template:
`
<div>
<h2>模板1</h2>
</div>
`,
}
}
})
</script>
4.组件模板的分离写法
介绍:创建组件构造器的时候的template很麻烦,可以用代替
两种方式写模板,用法如下:
<body>
<div id="app">
<cpn></cpn>
</div>
//method1
<template id="cpn">
<div>
<h2>我是模板标题</h2>
<p>我是模板文字</p>
</div>
</template>
//method2
<script type="text/x-template" id="cpn">
<div>
<h2>我是模板标题</h2>
<p>我是模板文字</p>
</div>
</script>
<script src="../js/vue.js"></script>
<script>
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
},
components: {
cpn: {
template: "#cpn",
}
}
})
</script>
</body>
两个的结果都是:
5.组件中数据以及数据格式的思考
介绍:我们new Vue对象的时候会写data,组件也有data,而且是组件自身的
意义:组件可以存放数据,才能体现组件复用的好处,复用时每个组件数据不能粘连
案例写法(用同一个组件写3个计数器,数据相互独立):
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>当前计数: {
{counter}}</h2>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
},
components: {
cpn: {
template: "#cpn",
//注意写法
data(){
return {
counter: 0
}
},
methods: {
increment(){
this.counter++;
},
decrement(){
this.counter--;
}
}
}
}
})
</script>
</body>
效果:
思考:为什么组件的数据能不互相粘连
举个例子:
思考以下代码会打印什么:
<script>
function f() {
return {
name: "aaa",
age: 18
}
}
let obj1 = f();
let obj2 = f();
obj1.name = "kobe"
console.log(obj1);
console.log(obj2);
</script>
结果:
我们可以发现,obj1 和 obj2 创建时调用了两个不同的地址空间,所以obj1和obj2的内容相互独立,数据没有粘,data同理
6.父组件传值给子组件
意义:开发场景中,通常是根组件接受后台传来的数据,然后再传递给下级子组件
小实例展示父组件如果传值子组件:
<body>
<div id="app">
<cpn :c-message="message" :c-movies="movies"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<li v-for="item in cmovies">{
{item}}</li>
</ul>
<h2>{
{cmessage}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const aaa = new Vue({
el: "#app",
data: {
message: '你好啊',
movies: ['海王', '海贼王', '海尔兄弟']
},
components: {
'cpn': {
template: "#cpn",
// method1 三种方法都可以,已debug
// props: ['cmessage', 'cmovies'],
//method2 可以进行类型判断
// props: {
// cmessage: String,
// cmovies: Array
// },
//method3 设置默认参数
props: {
cmessage: {
type: String,
default: '默认参数',
require: true //一定要传的参数
},
cmovies: {
type: Array,
default() {
return ['空数组1', '空数组2']
},
require: false //不一定要传的参数
}
},
data() {
return {
}
}
}
}
})
</script>
</body>
效果:
cpn没有被传参时:
cpn里有参数的时候:
另外,值得注意的是,html标签不能识别驼峰原则的属性,如果js里面属性是驼峰,应该这样:
html:
<cpn :c-message="message" :c-movies="movies"></cpn>
js:
props: {
cMessage: {
type: String,
default: '默认参数',
require: true //一定要传的参数
},
cMovies: {
type: Array,
default() {
return ['空数组1', '空数组2']
},
require: false //不一定要传的参数
}
},
7.子组件传值给父组件
意义:想象一个场景,子组件发生点击事件,页面另一处改变,就需要所点击组件传值给父
案例:点击按钮让父组件收到数据
逻辑流程:模板里v-for产生按钮→点击按钮触发点击事件(点击事件写在子组件里)→点击事件通过$emit函数发送从子组件接受到的数据→html在使用子组件的时候接受子组件传来的数据
<cpn @receive-message="receivedMessage"></cpn>
(至于为什么这样写:和此组件并列的div并不能收到数据,只有这种写法才能正确传递数据,应该是只有
子组件最外层,就是写到html里的那层(在dom层面会被解析)才能获取子组件内部所产生的数据)
→在父组件的方法receivedMessage里就可以展示收到的数据
<body>
<div id="app">
<cpn @receive-message="receivedMessage"></cpn>
</div>
<template id="cpn">
<div>
<button v-for="item in categories" @click="sendMessage(item)">{
{item.name}}</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
},
components: {
'cpn': {
template: '#cpn',
data() {
return {
categories: [
{
id: 'aaa', name: '热门推荐'},
{
id: 'bbb', name: '手机数码'},
{
id: 'ccc', name: '家用家电'},
{
id: 'ddd', name: '电脑办公'},
]
}
},
methods: {
sendMessage(item){
this.$emit('receive-message', item);
}
}
}
},
methods: {
receivedMessage(item){
console.log("received", item);
}
}
})
</script>
</body>
效果:
8.父子组件双向通讯
意义:想象一个需求,在一个组件里输入值,让父组件传过来的属性改变,同时父组件里的值也要改变,
思考:这无法直接改属性,假设属性是pnum,直接用this.pnum=xxxx(输入的值),会报错,子组件无法直接更改父组件的属性
,所以需要先把属性用数据data保存起来data(){ return{ dnum: this.pnum } },
然后再改变数据里面的值,同时输入时input标签添加输入函数@input=“changeNumAndSend”,修改数据同时传值给父组件,父组件再接受所改变的值,赋值给data里面传给子组件的对象,对象又改变传给子组件的属性prop
代码:
<body>
<div id="app">
<h2>num:{
{num}}</h2>
<cpn :pnum="num" @change-num="changeNum"></cpn>
</div>
<template id="cpn">
<div>
<h2>prop:{
{pnum}}</h2>
<h2>data:{
{dnum}}</h2>
<input type="text" @input="changeNumAndSend">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const aaa = new Vue({
el: "#app",
data: {
num: 8888,
},
methods: {
changeNum(newNum){
this.num = newNum;
}
},
components: {
'cpn': {
template: "#cpn",
props: {
pnum: Number,
},
data(){
return{
dnum: this.pnum + 1,
}
},
methods: {
changeNumAndSend(event){
this.dnum = parseInt(event.target.value);
this.$emit('change-num', this.dnum);
}
}
}
}
})
</script>
</body>
效果:
输入框里输入什么,num(父组件里面),prop(父组件传的属性),data(子组件数据)都随时变,data与输入函数牢牢绑定,第一个改变,num是输入函数顺便传值给父组件所以改变,prop是随着父组件数据改变所以变了
9.父组件访问子组件里的值
介绍:父组件访问子组件里的值有两种方式,第一种是用$children实现,直接在父组件的methods里面用就好了,以数组的形式返回
案例代码:
<body>
<div id="app">
<button @click="showChildrenData">在父组件里的按钮</button>
<cpn ></cpn>
<cpn ></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件的模板</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
},
methods: {
showChildrenData(){
console.log(this.$children[0].cdata);
this.$children[0].showMessage();
}
},
components: {
'cpn': {
template: "#cpn",
data() {
return {
cdata: "我是子组件里的一个data"
}
},
methods: {
showMessage(){
console.log("我是子组件里的一个函数");
}
}
}
}
})
</script>
</body>
结果:
成功调用子组件的data和methods,但是需要通过数组返回
$refs方法,直接访问绑定的标签,耦合度低:
代码:
<body>
<div id="app">
<button @click="showChildrenData">在父组件里的按钮</button>
<cpn ref="aaa"></cpn>
<cpn ref="bbb"></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件的模板</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
},
methods: {
showChildrenData(){
console.log(this.$refs);
console.log(this.$refs.aaa.cdata);
this.$refs.aaa.showMessage();
}
},
components: {
'cpn': {
template: "#cpn",
data() {
return {
cdata: "我是子组件里的一个data"
}
},
methods: {
showMessage(){
console.log("我是子组件里的一个函数");
}
}
}
}
})
</script>
</body>
效果:
10.子组件访问父组件里面的值
介绍:子组件访问里的值用$parent
案例:
<body>
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件的模板</h2>
<button @click="visitParent">我是子组件里面的一个按钮</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
},
methods: {
},
components: {
'cpn': {
template: "#cpn",
data() {
return{
}
},
methods: {
visitParent(){
console.log(this.$parent);
console.log(this.$parent.message);
console.log(this.$root);
console.log(this.$root.message);
}
}
}
}
})
</script>
</body>
结果:
$root可以访问根组件的值
11.插槽的基本用法
介绍:插槽就是组件中可变的标签,比如一个组件复用两次,两次使用中所用到的标签略有差异,那就把可变的标签用插槽表示
案例:三个组件对比:不使用插槽,插槽放一个标签,插槽放两个标签
<body>
<div id="app">
<cpn></cpn>
<cpn><h2>这是slot</h2></cpn>
<cpn>
<h2>这是h2-slot</h2>
<p>这是p-slot</p>
</cpn>
</div>
<template id="cpn">
<div>
<h2>模板标题</h2>
<p>模板内容</p>
<slot></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
},
components: {
'cpn': {
template: "#cpn",
}
}
})
</script>
</body>
效果:
12.给插槽命名
意义:假设一个场景,一个组件里有三个插槽,每次使用三个插槽里的内容都不相同,那就要给每个插槽都命名,某个标签代替插槽的时候通过插槽名字找到对应插槽然后再替代
代码:
<body>
<div id="app">
<cpn></cpn>
<cpn><span slot="left">标题</span></cpn>
<cpn><span slot="left">标题</span> <button slot="right">按钮</button></cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>左边插槽</span></slot>
<slot name="center"><span>中间插槽</span></slot>
<slot name="right"><span>右边插槽</span></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
},
components: {
'cpn': {
template: "#cpn",
}
}
})
</script>
</body>
效果:
13.作用域插槽
编译的作用域:
我们在使用组件的时候,<cpn v-show="isShow"></cpn>
isShow函数写在Vue里面,
组件里面的函数写在模板里面,写在组件的methods里面
作用域插槽没明白有什么用,等明白了再改
就是读取子组件的值,渲染到父组件,看弹幕说$children要渲染完成才能使用,这个在渲染前就加载好
代码:
<body>
<div id="app">
<cpn></cpn>
<cpn>
<template slot-scope="slot">
<span v-for="item in slot.data"> {
{item}} </span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="pLanguages">
<ul>
<li v-for="item in pLanguages">{
{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
},
components: {
'cpn': {
template: "#cpn",
data(){
return{
pLanguages: ["java", "c++", "Python"]
}
}
}
}
})
</script>
</body>
效果: