文章目录
1.全局组件与局部组件
组件的意义:写一个可以复用的小模块,
举例:写一段可以复用的标签
注册组件基本步骤
- 创建组件构造器(调用Vue.extend()方法)
- 调用Vue.extend()创建的是一个组件构造器。
- 通常在创建组件构造器时,传入template代表我们自定义组件的模板。
- 该模板就是在使用到组件的地方,要显示的HTML代码。
- 事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
- 注册组件(分为全局和局部,调用Vue.component()方法)
- 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
- 所以需要传递两个参数:1、注册组件的标签名 2、组件构造器
- 使用组件(在Vue实例的作用范围内使用组件)
写法代码:
<body>
<div id="app">
<!--未注册,不可使用-->
<cpn></cpn>
</div>
<div id="app2">
<!--3.使用组件-->
<cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpn = Vue.extend({
template:
`
<div>
<h2>模板</h2>
<h2>模板</h2>
</div>
`
})
// 2.全局注册组件,可以在多个Vue实例下面使用,没有在Vue实例的作用范围内使用组件的限制
// 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.父组件与子组件
介绍:如果一个组件能调用另一个组件,那么这个组件是父组件,被调用的是子组件
cpn2(模板2)并不能直接被id=app的div调用,因为他是在模板1生成的时候注册的,模板1生成时可以调用,但是模板1注册的时候(在new vue)不会顺带模板2,除非模板2也在new vue里注册
代码:
<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是父组件
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.组件中数据以及数据格式的思考
组件不可以直接访问Vue实例中的数据,Vue组件的数据应该放在组件对象里
组件对象也有一个data属性(也可以有methods,props属性,下面我们有用到)
只是这个data属性必须是一个函数,而且这个函数返回一个对象,对象内部保存着数据
为什么必须是函数:用data:{}的写法,多个组件引用同一个data对象,影响其他组件,data(){}的写法每次都会返回一个新的对象,然后各自组件用各自的对象就不会互相影响了。(想要使用同一个对象参考如果我想obj1,obj2指向同一个对象?)
介绍:我们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>
Vue.component('cpn', {
template: "#cpn",
data() {
return {
counter: 0
}
},
// data:{}不可以这样
methods: {
increment() {
this.counter++;
},
decrement() {
this.counter--;
}
}
});
const aaa = new Vue({
el: "#app",
data: {
message: "hello,guys",
}
})
</script>
</body>
效果:
思考:我们写的三个组件是不是使用同一个data对象
- 不是
- 原因:创建组件的时候会调用data函数,每次调用都会return一个新的对象
举个例子:
下面代码obj1和obj2是不是同一个对象?
不是,函数每次执行的时候都会在栈空间创建新的对象来返回,所以obj1和obj2指向不同内存地址
<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指向同一个对象?
- obj本质上是个内存地址(0x100),函数返回对象的时候本质上返回obj的内存地址,所以一旦调用都返回内存地址,obj1/obj2内存地址都是0x100,都指向obj
const obj = {
name: 'aaa',
age: 18
}
function f() {
return obj
}
let obj1 = f()
let obj2 = f()
obj1.name = 'kobe'
console.log(obj1);
console.log(obj2);
父子组件的通信
- 子组件是不能引用父组件或者Vue实例的数据的。
- 在开发中,往往一些数据确实需要从上层传递到下层:
- 比如在一个页面中,我们从服务器请求到了很多的数据。其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)
- 如何进行父子组件间的通信呢?Vue官方提到
- 通过props向子组件传递数据
- 通过事件向父组件发送消息
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>
<!-- 不需要传参数,因为不是@click,这里不写参数不会给事件mouseclick对象,会给emit的参数-->
</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>
效果:
父子传值总结
- cpn标签中既可以用来父传子,
<cpn :c-message="message" :c-movies="movies"></cpn>
- 也可以用来字传父
<cpn @receive-message="receivedMessage"></cpn>
接收emit事件 - 可以看作父子传值的桥梁
8.父子组件双向通讯
意义:想象一个需求,在一个组件里输入值,让父组件传过来的属性改变,同时父组件里的值也要改变,
思考:这无法直接改属性,假设属性是pnum,直接用this.pnum=xxxx(输入的值),会报错,子组件无法直接更改父组件传来的属性
,所以需要先把属性用数据data保存起来data(){ return{ dnum: this.pnum } },
然后再改变数据里面的值,同时输入时input标签添加输入函数@input=“changeNumAndSend”,修改数据同时传值给父组件,父组件再接受所改变的值,赋值给data里面传给子组件的对象,对象又改变传给子组件的属性prop
代码:
<body>
<div id="app">
<cpn :pnum="num" @send-change="receiveChange"></cpn>
</div>
<template id="cpn">
<div>
<h2>pnum:{{pnum}}</h2>
<h2>dnum:{{dnum}}</h2>
<input type="text" :value="dnum" @input="inputEvent">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const aaa = new Vue({
el: "#app",
data: {
num: 8888,
},
methods: {
receiveChange(item) {
this.num = item;
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
dnum: this.pnum
}
},
props: {
pnum: Number
},
methods: {
inputEvent(event) {
this.dnum = parseInt(event.target.value);
this.$emit('send-change', 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方法,直接访问绑定的标签,耦合度低:
- this.$refs是对象类型,默认是空对象,在标签中有属性ref='xxx’才有内容
代码:
<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.插槽的基本用法
介绍:插槽就是组件中可变的标签,比如一个组件复用两次,两次使用中所用到的标签略有差异,那就把可变的标签用插槽表示
- 插槽的的默认值:这样定义插槽
<slot><span>哈哈哈</span></slot>
插槽内不放东西就是默认哈哈哈
- 如果插槽有多个值,同时放入组件进行替换,一起作为替换元素
案例:三个组件对比:不使用插槽,插槽放一个标签,插槽放两个标签
<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里面
14.作用域插槽
通过插槽来传值
就是读取子组件的值,渲染到父组件,$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>
效果: