组件化
1. 注册组件的基本步骤
- 创建组件构造器:调用
Vue.extend()
方法 - 注册组件:调用
Vue.component()
方法 - 使用组件:在
Vue实例
的作用范围内使用组件
注
:这种注册方法得到的是全局组件,可以在多个 Vue 实例下面使用
const cpnConstructor = Vue.extend({
template: `
<div>
<h3>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h3>
<h4>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h4>
<h5>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h5>
<h6>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h6>
</div>
`
})
Vue.component('my-cpn', cpnConstructor);
2. 全局组件和局部组件
- 全局组件:可以在多个 Vue 实例下面使用
Vue.component('my-cpn', cpnConstructor);
- 局部组件:只能在该 Vue 实例对象下使用
const app = new Vue({
el: "#app",
components: {
'cpn': cpnConstructor
}
})
3. 父组件和子组件
<div id="app">
<cpnfather></cpnfather>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 子组件
const cpnSonCon = Vue.extend({
template: `
<div>
<h5>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h5>
<h6>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h6>
</div>
`
})
// 父组件
const cpnFatherCon = Vue.extend({
template: `
<div>
<h3>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h3>
<h4>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h4>
<cpnson></cpnson>
</div>
`,
components: { // 父组件中注册子组件
'cpnson': cpnSonCon
}
})
const app = new Vue({
el: "#app",
components: {
'cpnfather': cpnFatherCon,
'cpnson': cpnSonCon, // 只有在 root 组件中注册了子组件,才能单独使用子组件
}
})
</script>
注:遇到的问题
——注册的标签名不能带大写字母
4. 注册组件语法糖
省去调用 Vue.extend()
的一个步骤,直接使用一个对象来代替
- 全局组件
Vue.component('my-cpn', {
template: `
<div>
<h3>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h3>
<h4>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h4>
<h5>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h5>
<h6>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h6>
</div>
`
});
- 局部组件
const app = new Vue({
el: "#app",
components: {
'cpn': {
template: `
<div>
<h3>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h3>
<h4>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h4>
<h5>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h5>
<h6>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h6>
</div>
`
},
}
})
5. 组件模板抽离的方法
script标签
template标签
<body>
<div id="app">
<cpn></cpn>
<cpn2></cpn2>
</div>
<script type="text/x-template" id="cpn">
<div>
<h3>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h3>
<h4>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h4>
</div>
</script>
<template id="cpn2">
<div>
<h5>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h5>
<h6>https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js</h6>
</div>
</template>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
Vue.component('cpn', {
template: "#cpn"
});
const app = new Vue({
el: "#app",
components: {
'cpn2': {
template: '#cpn2'
}
}
})
</script>
6. 组件的data属性
- 组件的 data 属性是一个函数,返回的是一个对象,对象内部保存着数据
- 目的:每个组件都有属于自己的对象,如果 data 属性是一个对象,那么多个组件就会共享该 data 对象
<body>
<div id="counter">
<count></count>
<count></count>
<count></count>
<count></count>
</div>
<template id="app">
<div>
<h2>{{num}}</h2>
<button @click="add">+</button>
<button @click="sub">-</button>
</div>
</template>
<script>
Vue.component('count', {
template: '#app',
data() {
return {
num: 0,
}
},
methods: {
add() {
this.num++;
},
sub() {
this.num--;
}
}
})
const app = new Vue({
el: "#counter",
})
</script>
</body>
7. 父子组件间的通信
1)父组件通过 props
向子组件传递数据,方式有两种
- 字符串数组,数组中的字符串就是传递时的名称
- 对象,对象可以设置传递时的类型(即类型限制),也可以设置默认值等
<!--- 父组件使用字符串数组与子组件进行通信 --->
<body>
<div id="app">
<cpnfather :fmovies="movies"></cpnfather>
</div>
<!-- 父组件模板 -->
<template id="movies">
<ul>
<li v-for="item in fmovies">{{item}}</li>
<cpnson :cmovies="fmovies"></cpnson>
</ul>
</template>
<!-- 子组件模板 -->
<template id="cmovies">
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 子组件
const cpnson = {
template: '#cmovies',
props: ['cmovies'],
data() {
return {};
}
}
// 父组件
const cpnfather = {
template: '#movies',
data() {
return {};
},
props: ['fmovies'], // 数组中的字符串就是传递时的名称
components: { //注册子组件
cpnson,
}
}
const app = new Vue({
el: "#app",
data: {
movies: ['盗梦空间', '大兵瑞恩', '窈窕淑女', '星际穿越', '海上钢琴师', '死亡诗社'],
},
components: { // 注册父组件
cpnfather,
}
})
</script>
</body>
<body>
<div id="app">
<cpnfather :fmovies="movies"></cpnfather>
</div>
<template id="movies">
<ul>
<li v-for="item in fmovies">{{item}}</li>
<cpnson :cmovies="fmovies"></cpnson>
</ul>
</template>
<template id="cmovies">
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const cpnson = {
template: '#cmovies',
props: { // 使用对象进行传递
cmovies: { // 对象还可以包括类型限制,必传属性等属性
type: Array,
required: true,
}
},
data() {
return {};
}
}
const cpnfather = {
template: '#movies',
data() {
return {};
},
props: {
fmovies: Array, // 类型限制
}, // 数组中的字符串就是传递时的名称
components: {
cpnson,
}
}
const app = new Vue({
el: "#app",
data: {
movies: ['盗梦空间', '大兵瑞恩', '窈窕淑女', '星际穿越', '海上钢琴师', '死亡诗社'],
},
components: {
cpnfather,
}
})
</script>
</body>
- 使用对象进行传递数据时的多种写法
props: {
// 基础的数据检查,此项为 null 时,匹配任何类型
propA: Number,
// 匹配多个类型
propB: [String, number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default() {
return { message: 'hello' };
}
},
// 自定义验证函数
propF: {
validator(value) {
return ['sucess', 'warning', 'danger'].indexOf(value) !== -1;
}
}
}
// 自定义类型
function Person (firstName, lastName) {
this.firstName= firstName;
this.lastName= lastName;
}
Vue.component('blog-post', {
props: {
author: Person
}
})
- 父传子(props的驼峰标识):v-bind 是不支持使用驼峰标识的,例如
cUser
要改成c-user
或cuser
2)子组件通过 自定义事件
向父组件发送消息
- 子组件点击按钮,传递按钮元素内的内容
<body>
<div id="app">
<cpnson @cpnclick="cpnclick"></cpnson>
</div>
<template id="cmovies">
<div>
<button @click="btnclick(item)" v-for="(item,index) in movies" :key="index">{{item.name}}</button>
</div>
</template>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const cpnson = {
template: '#cmovies',
data() {
return {
movies: [{
id: 1,
name: '盗梦空间'
}, {
id: 2,
name: '大兵瑞恩'
}, {
id: 3,
name: '窈窕淑女'
}, {
id: 4,
name: '星际穿越'
}, {
id: 5,
name: '海上钢琴师'
}, {
id: 6,
name: '死亡诗社'
}],
};
},
methods: {
btnclick(item) {
this.$emit('cpnclick', item);
}
}
}
const app = new Vue({
el: "#app",
data() {
return {};
},
methods: {
cpnclick(item) {
console.log("cpnclick", item)
}
},
components: {
cpnson,
},
})
</script>
- 子组件是按钮,点击子组件的按钮,改变父组件的值
<body>
<div id="app">
<div>{{num}}</div>
<cpn @item-Click="op"></cpn>
</div>
<template id="operation">
<div>
<button @click="decrement">-</button>
<button @click="increment">+</button>
</div>
</template>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const cpn = {
template: '#operation',
data() {
return {
add: 'add',
sub: 'sub'
};
},
methods: {
increment () {
this.$emit('item-Click', this.add);
},
decrement () {
this.$emit('item-Click', this.sub);
},
}
}
const app = new Vue({
el: '#app',
data() {
return {
num: 0,
};
},
methods: {
op(e) {
switch(e) {
case 'add': this.num++;
break;
case 'sub': this.num--;
break;
default:
break;
}
}
},
components: {
cpn
}
})
</script>
- 自定义事件的流程:
- 在子组件中,通过
$eemit()
来触发事件 - 在父组件中,通过
v-on
来监听子组件
- 在子组件中,通过
3) 父子组件通信小案例
父组件传递数据到子组件,子组件中的输入框中输入数字,修改父组件中的数据
- input 实现
<body>
<div id="app">
<h3>num1: {{num1}}</h3>
<h3>num2: {{num2}}</h3>
<cpn :cnum1="num1" :cnum2="num2" @fnum1change="fnum1change" @fnum2change="fnum2change"></cpn>
</div>
<template id="cpn">
<div>
<h3>{{cnum1}}</h3>
<h3>{{inum1}}</h3>
<input type="text" v-model="inum1" @input="num1change">
<h3>{{cnum2}}</h3>
<h3>{{inum2}}</h3>
<input type="text" v-model="inum2" @input="num2change">
</div>
</template>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0,
},
methods: {
fnum1change(value) {
this.num1 = parseFloat(value);
},
fnum2change(value) {
this.num2 = parseFloat(value);
}
},
components: {
cpn: {
template: '#cpn',
props: {
cnum1: {
type: Number,
default: 9,
},
cnum2: {
type: Number,
default: 9,
}
},
data() {
return {
inum1: this.cnum1,
inum2: this.cnum2,
};
},
methods: {
num1change(event) {
this.$emit('fnum1change', event.target.value);
},
num2change(event) {
this.$emit('fnum2change', event.target.value);
}
}
}
}
})
</script>
- show实现
<body>
<div id="app">
<h2>父组件data</h2>
<h3>num1: {{num1}}</h3>
<h3>num2: {{num2}}</h3>
<cpn :cnum1="num1" :cnum2="num2" @fnum1change="fnum1change" @fnum2change="fnum2change"></cpn>
</div>
<template id="cpn">
<div>
<h2>子组件props</h2>
<h3>cnum1: {{cnum1}}</h3>
<h3>cnum2: {{cnum2}}</h3>
<h2>子组件data</h2>
<h3>inum1: {{inum1}}</h3>
<h3>inum2: {{inum2}}</h3>
<input type="text" v-model="inum1"><br />
<input type="text" v-model="inum2">
</div>
</template>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0,
},
methods: {
fnum1change(value) {
this.num1 = parseFloat(value);
},
fnum2change(value) {
this.num2 = parseFloat(value);
}
},
components: {
cpn: {
template: '#cpn',
props: {
cnum1: {
type: Number,
default: 9,
},
cnum2: {
type: Number,
default: 9,
}
},
data() {
return {
inum1: this.cnum1,
inum2: this.cnum2,
};
},
watch: {
inum1(newValue) {
this.$emit('fnum1change', newValue);
},
inum2(newValue) {
this.$emit('fnum2change', newValue);
}
}
}
}
})
</script>
- show
侦听器:监听数据变化,一般只监听一个变量或数组
使用场景:watch(异步场景
),computed(数据联动
)
8. 父子组件的访问方式
有时候需要直接操作子组件的方法,或是属性,此时需要用到 $children
和 $ref
<!-- 父组件 -->
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn ref="aaa"></cpn>
<button @click="btnClick" >按钮</button>
</div>
<!-- 子组件 -->
<template id="cpn">
<div>我是子组件</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data() {
return {
message:"hello"
}
},
methods: {
btnClick(){
// 1.children
// console.log(this.$children[0].showMessage)
// for (let cpn of this.$children) {
// console.log(cpn.showMessage)
// }
// 2.$ref
console.log(this.$refs.aaa.name)
}
},
components: {
cpn: {
template: "#cpn",
data() {
return {
name:"我是子组件的name"
}
},
methods: {
showMessage(){
console.log("showMessage");
}
},
}
},
})
</script>
$children
方式
console.log(this.$children[0].showMessage)
for (let cpn of this.$children) {
console.log(cpn.showMessage)
}
使用
this.$children
直接获取**当前实例的直接子组件,需要注意$children
并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用$children
来进行数据绑定,考虑使用一个数组配合v-for
来生成子组件,并
且使用 Array 作为真正的来源。
$refs
方式
<!-- 先定义子组件,直接调用 -->
<cpn ref="aaa"></cpn>
$parent
方式
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn ref="aaa"></cpn>
</div>
<template id="cpn">
<div>
子组件消息:{{message}}
<button @click="btnClick" >子组件按钮</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data() {
return {
message:"我是父组件消息"
}
},
components: {
cpn: {
template: "#cpn",
data() {
return {
message:"我是子组件的name"
}
},
methods: {
btnClick(){
console.log("子组件按钮被点击")
// 1.访问父组件$parent
this.message = this.$parent.message
// 2.访问根组件$root
console.log(this.$root)
console.log(this.$root.message)
}
}
}
})
</script>
</body>