上期回顾
- 条件判断(v-if、v-show)的基本使用、开发时选择
- 条件渲染案例(input添加key的区别)
- 循环遍历(v-for)
- 表单绑定(v-model)
- lazy修饰符
- number修饰符
- trim修饰符
- 拓展:数组的响应式方法
什么是组件化:
独立的,可复用的,整体化的一个功能模块
为什么要组件化:
1.实现功能模块的复用
2.开发单页面复杂应用
3.高效执行
注册组件
三大步骤
1.创建组件构造器( Vue.extend() )
2.注册组件( Vue.component() )
3.使用组件
- 代码演练
<div id="app">
<!-- 第三步:使用组件 -->
<my-cpn></my-cpn>
<!-- 错误的使用方式 -->
<myCpn></myCpn>
</div>
<script src="../js/vue.js"></script>
<script>
//第一步:创建组件构造器
let cpn = Vue.extend({
template: `
<div>
<h2>我是组件</h2>
</div>
`
})
//第二步:注册组件
Vue.component("myCpn", cpn)
const app = new Vue({
el: "#app",
data: {
}
})
</script>
注意:
第三步不能使用小驼峰命名,大写字母变为小写字母,前面加 "-"
Vue.extend({template})
Vue.extend():返回一个组件构造器
template:代表自定义组件的html模板
Vue.vomponent("组件命名",组件构造器)
Vue.vomponent():注册组件
全局组件和局部组件
- 代码演示
<div id="app1">
<!-- 全局 -->
<global-cpn></global-cpn>
<!-- 局部 -->
<part-cpn></part-cpn>
</div>
<div id="app2">
<!-- 全局 -->
<global-cpn></global-cpn>
<!-- 局部 -->
<part-cpn></part-cpn>
</div>
<script src="./js/vue.js"></script>
<script>
//1.创建全局组件构造器
let cpn1 = Vue.extend({
template: `
<div>
<h2>我是全局组件</h2>
</div>
`
})
//1.创建局部组件构造器
let cpn2 = Vue.extend({
template: `
<div>
<h2>我是局部组件</h2>
</div>
`
})
//2.注册全局组件
Vue.component("globalCpn", cpn1)
const app1 = new Vue({
el: "#app1",
//第二步:注册局部组件
components: {
"partCpn": cpn2
}
})
const app2 = new Vue({
el: "#app2"
})
</script>
父组件和子组件
- 代码演示
<div id="app">
<!-- 使用父组件 -->
<father-cpn></father-cpn>
</div>
<script src="./js/vue.js"></script>
<script>
//1.创建子组件构造器
let cpn2 = Vue.extend({
template: `
<div>
<h2>我是子组件</h2>
</div>
`
})
//1.创建父组件构造器
let cpn1 = Vue.extend({
template: `
<div>
<h2>我是父组件</h2>
<!-- 使用子组件 -->
<son-cpn></son-cpn>
</div>
`,
//在父组件中挂载子组件
components: {
"sonCpn": cpn2
}
})
const app1 = new Vue({
el: "#app",
//在全局组件中挂载父组件
components: {
"fatherCpn": cpn1
}
})
</script>
组件的简写方式
- 代码演示
<div id="app">
<!-- 使用全局组件 -->
<global-cpn></global-cpn>
<!-- 使用局部组件 -->
<part-cpn></part-cpn>
</div>
<script src="./js/vue.js"></script>
<script>
// 创建全局组件
Vue.component("globalCpn", {
template: `
<div>
<h2>我是全局组件</h2>
</div>
`
})
const app1 = new Vue({
el: "#app",
// 创建局部组件
components: {
"partCpn": {
template: `
<div>
<h2>我是局部组件</h2>
</div>
`
}
}
})
</script>
省去了Vue.extend()的步骤,直接用一个对象代替
模板的两种分离写法
1.使用<script>标签
- 代码演示
<div id="app">
<my-cpn></my-cpn>
</div>
<!-- 使用<script>标签,注意加上type属性和id属性 -->
<script type="text/x-template" id="myCpn">
<div>
<h2>我是组件</h2>
</div>
</script>
<script src="./js/vue.js"></script>
<script>
const app1 = new Vue({
el: "#app",
// 创建局部组件
components: {
"myCpn": {
template: "#myCpn"
}
}
})
</script>
2.使用<template>标签(推荐)
- 代码演示
<div id="app">
<my-cpn></my-cpn>
</div>
<!-- 使用<template>标签,注意加上id属性 -->
<template id="myCpn">
<div>
<h2>我是组件</h2>
</div>
</template>
<script src="./js/vue.js"></script>
<script>
const app1 = new Vue({
el: "#app",
// 创建局部组件
components: {
"myCpn": {
template: "#myCpn"
}
}
})
</script>
注意:
使用两种标签,主要是将template中的html模板抽离出去,所以,两种标签一定要加上id属性,用于template的绑定
组件数据的处理
- 代码演示
<div id="app">
{{message1}} - {{message2}}
<my-cpn></my-cpn>
</div>
<template id="myCpn">
<div>
<h2>{{message1}}</h2>
<h2>{{message2}}</h2>
</div>
</template>
<script src="./js/vue.js"></script>
<script>
cpn = {
"myCpn": {
template: "#myCpn",
// 数据存放在组件内部,组件可以访问,全局不能
data() {
return {
message2: "组件内的数据"
}
},
}
}
const app1 = new Vue({
el: "#app",
components: cpn,
// 数据存放在实例的data内,全局可以访问在组件中无法访问
data() {
return {
message1: "实例内的数据"
}
},
})
</script>
注意:
组件中data必须是函数:这样可以保证组件每次使用的数据不会被别的组件更改,保证各组件间的数据互不干扰,data不是函数时,会报错!!!
组件间的数据传递(父传子)
props基本用法
- 代码演示
<div id="app">
<!-- 1.通过v-bind绑定数据message -->
<my-cpn :info="message"></my-cpn>
</div>
<template id="myCpn">
<div>
<!-- 3.展示组件内props的数据 info -->
<h2>{{info}}</h2>
</div>
</template>
<script src="./js/vue.js"></script>
<script>
cpn = {
"myCpn": {
template: "#myCpn",
// 2.接收组件标签绑定的数据
props: ["info"]
}
}
const app1 = new Vue({
el: "#app",
components: cpn,
data: {
message: "hello Vue"
}
})
</script>
注意数据传递过程:
1.通过v-bind绑定数据messag
2.接收组件标签绑定的数据
3.展示组件内props的数据 info
props数据验证
- 代码演示
<div id="app">
<my-cpn :info="message" :addNum="add()" :num="num3"></my-cpn>
</div>
<template id="myCpn">
<div>
<h2>{{info}}</h2>
<h2>{{addNum}}</h2>
<h2>{{num}}</h2>
<h2>{{str}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello Vue",
num1: 3,
num2: 5,
num3: 10,
str: "test"
},
methods: {
add() {
console.log(this.num1 + this.num2)
}
},
components: {
"my-cpn": {
template: "#myCpn",
props: {
// key:类型
info: String,
addNum: Function,
num: {
type: Number,
// 设为必填值
required: true,
},
str: {
type: String,
// 设置默认值
default: "我是默认值"
}
}
}
}
})
</script>
- props支持验证类型
String
Number
Array
Boolean
Function
Object
Date
Symbol
组件间的数据传递(子传父)
- 代码演示
<div id="app">
<h2>{{num}}</h2>
<!-- 第四步: 将子组件传出的函数转为自定义函数,并绑定自定义事件的实现函数-->
<son-cpn :amount="num" @add="change" @cut="change"></son-cpn>
</div>
<template id="myCpn">
<div>
<!-- 第一步: 绑定事件 -->
<button @click="add">加10</button>
<button @click="cut">减10</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
num: 1
},
methods: {
// 第五步: 实现自定义事件的函数
// count 是第三步传入的参数
change(count) {
this.num = count
}
},
components: {
"sonCpn": {
template: "#myCpn",
props: {
amount: {
type: Number,
required: true,
}
},
data() {
return {
count: this.amount
}
},
methods: {
// 第二步: 事件函数的实现
add() {
this.count += 10
// this.amount += 10 避免直接改变属性,因为当父组件重新呈现时,该值将被覆盖。
// 第三步: 使用 emit("函数名",参数) 通知父组件,并传递参数
this.$emit("add", this.count)
},
cut() {
this.count -= 10
// this.amount += 10 避免直接改变属性,因为当父组件重新呈现时,该值将被覆盖。
// 第三步: 使用 emit("函数名",参数) 通知父组件,并传递参数
this.$emit("cut", this.count)
}
}
}
}
})
数据传递(子传父)过程
1.在子组件中,通过$emit("事件名",参数)来触发事件。
2.在父组件中,通过v-on来监听子组件事件。
父组件直接访问子组件的数据($children、$refs)
- 代码演示
<div id="app">
<f-cpn></f-cpn>
</div>
<!-- 子组件模板 -->
<template id="zCpn">
<div>
<h2>我是子组件</h2>
<h2>{{info}}</h2>
<h2>调用方法:{{mult}}</h2>
</div>
</template>
<!-- 父组件模板 -->
<template id="fCpn">
<div>
<h2>我是父组件</h2>
<!-- 通过ref给某一个子组件绑定一个特定的ID -->
<z-cpn ref="child"></z-cpn>
<button @click="access$children">通过$children访问子组件</button>
<button @click="access$refs">通过$refs访问子组件</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
//子组件实例
let zCpn = {
template: "#zCpn",
data() {
return {
info: "我是子组件数据",
num: 2
}
},
computed: {
mult() {
return this.num * 3
}
}
}
const app = new Vue({
el: "#app",
data: {
},
components: {
// 注册父组件
"f-cpn": {
template: "#fCpn",
components: {
// 注册子组件
"z-cpn": zCpn
},
methods: {
access$children() {
// 通过$children访问子组件
console.log(this.$children)
console.log(this.$children[0].info)
console.log(this.$children[0].mult)
// console.log(this.$children[0].mult()) ❌错误写法,mult是计算属性,不是函数
},
access$refs(){
// 通过$refs访问子组件
console.log(this.$refs)
console.log(this.$refs.child)
console.log(this.$refs.child.info)
console.log(this.$refs.child.mult)
// console.log(this.$refs.child.mult()) ❌错误写法,mult是计算属性,不是函数
}
}
}
}
})
</script>
总结:
- $children需要通过子组件的下标($children[index]),才能访问该子组件,易出错,
- $refs是通过ref绑定的ID访问子组件($refs.ID),更加精确到位,推荐使用$refs
子组件直接访问父组件的数据($parent)
- 代码演示
<div id="app">
<f-cpn></f-cpn>
</div>
<!-- 子组件模板 -->
<template id="zCpn">
<div>
<h2>我是子组件</h2>
<button @click="getFInfo">点击获取父组件的信息</button>
</div>
</template>
<!-- 父组件模板 -->
<template id="fCpn">
<div>
<h2>我是父组件</h2>
<z-cpn></z-cpn>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
//子组件实例
let zCpn = {
template: "#zCpn",
methods: {
getFInfo() {
console.log(this.$parent)
console.log(this.$parent.info)
this.$parent.show()
}
}
}
const app = new Vue({
el: "#app",
data: {
},
components: {
// 注册父组件
"f-cpn": {
template: "#fCpn",
components: {
// 注册子组件
"z-cpn": zCpn
},
data() {
return {
info: "我是父组件信息"
}
},
methods: {
show(){
console.log("通过$parent访问父组件的方法")
}
}
}
}
})
</script>
注意:
在开发中尽量避免子组件通过$parent直接访问父组件的数据,因为这样耦合度太高了。而且这样会使父组件中的状态将变得飘忽不定,很不利于我的调试和维护。
插槽(slot)的使用
插值基本用法
- 代码演示
<div id="app">
<div>==========单个slot使用==========</div>
<my-cpn1></my-cpn1>
<my-cpn1>
<!-- 填入内容替换插槽 -->
<h2>hello vue</h2>
</my-cpn1>
<div>==========具名solt使用==========</div>
<!-- 如果my-cpn2内部没有内容,具名插槽不会显示,不具名插槽会显示,如my-cpn1 -->
<my-cpn2></my-cpn2>
<!-- 如果my-cpn2内部所有的标签都绑定了具名插槽,则不具名的插槽不会显示 -->
<my-cpn2>
<div name="content1"><h2>替换文字1</h2></div>
<div name="content2"><h2>替换文字2</h2></div>
<div name="content3"><h2>替换文字3</h2></div>
</my-cpn2>
</div>
<template id="mySlot1">
<div>
<!-- 设置一个插槽 -->
<slot>默认内容</slot>
</div>
</template>
<template id="mySlot2">
<div>
<!-- 设置一个插槽 -->
<slot><h2>默认内容3</h2></slot>
<!-- 设置三个具名插槽 -->
<slot slot="content1"><h2>默认内容1</h2></slot>
<slot slot="content2"><h2>默认内容2</h2></slot>
<slot slot="content3"><h2>默认内容3</h2></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
components: {
myCpn1: {
template: "#mySlot1"
},
myCpn2: {
template: "#mySlot2"
}
}
})
</script>
slot数据传递
- 代码演示
<div id="app">
<my-cpn>
<!-- 第二步: 接收数据 -->
<template slot-scope="props">
<ul>
<!-- 第三步: 使用数据 -->
<li v-for="(value,index) in props.info">{{index}} - {{value}}</li>
</ul>
</template>
</my-cpn>
</div>
<template id="mySlot">
<div>
<!-- 第一步: 绑定数据 -->
<slot :info="course"></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: "#mySlot",
data() {
return {
course: {
1001: "Python入门",
1002: "数据结构与算法",
1003: "离散数学",
1004: "概率论",
1005: "形式与政策",
}
}
}
}
const app = new Vue({
el: "#app",
components: {
myCpn: cpn
}
})
</script>
注意:
编译作用域(补充)
- 代码演示
<div id="app">
<my-cpn v-if="isShow">
</my-cpn>
</div>
<template id="mySlot">
<div>
<slot>默认数据</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: "#mySlot",
data() {
return {
isShow: false
}
}
}
const app = new Vue({
el: "#app",
components: {
myCpn: cpn
},
data: {
isShow: true
}
})
</script>
注意:任意组件模板的所有东西都会在该模板作用域内编译,即变量会从该实例中寻找