- 组件用于封装页面的部分功能,将功能的 html、css、js 代码封装为整体
- 提高代码的复用性和可维护性
- 组件使用时为自定义 HTML 标签形式,通过组件名作为自定义标签名
组件注册
全局注册
- 全局注册的组件在注册后可以用于任意实例或组件中
⚠️:全局注册必须设置在根 Vue 实例创建之前
<body>
<div id="app">
<p>{{ value }}</p>
<my-com></my-com>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('my-com', {
template: '<div>这是 my-com 组件</div>'
})
new Vue({
el: '#app',
data: {
value: 'test'
}
})
</script>
</body>
组件基础
组件命名规则
- kebab-case:'my-component’
- PascalCase:‘MyComponent’
<body>
<div id="app">
<p>{{ value }}</p>
<my-com-a></my-com-a>
<my-com-b></my-com-b>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('my-com-a', {
template: '<div>这是 my-com-a 组件</div>'
})
Vue.component('MyComB', {
template: '<div>这是 my-com-b 组件</div>'
})
new Vue({
el: '#app',
data: {
value: 'test'
}
})
</script>
</body>
template 选项
- template 选项用于设置组件的结构,最终被引入根实例或其他组 件中
⚠️:组件必须只有一个根元素
<body>
<div id="app">
<my-com-b></my-com-b>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('MyComB', {
template:
`<ul>
<li>template 选项 test</li>
</ul>`
})
new Vue({
el: '#app',
data: {
value: 'test'
}
})
</script>
</body>
data 选项
- data 选项用于存储组件的数据,与根实例不同,组件的 data 选项必须为函数,数据设置在返回值对象中
- 这种实现方式是为了确保每个组件实例可以维护一份被返回对象的独立的拷贝,不会相互影响
<body>
<div id="app">
<my-com-b></my-com-b>
<my-com-b></my-com-b>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('MyComB', {
template:
`<div>
<h1> {{ title }} </h1>
</div>
`,
data () {
return {
title: '这是标题'
}
}
})
new Vue({
el: '#app',
data: {
value: 'test'
}
})
</script>
</body>
局部注册
- 局部注册的组件只能用在当前实例或组件中
<body>
<div id="app">
<my-com-b></my-com-b>
<my-com-b></my-com-b>
</div>
<script src="lib/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
value: 'test'
},
components: {
'my-com': {
template:
`<div>
<h1> {{ title }} </h1>
</div>
`,
data () {
return {
title: '这是标题'
}
}
}
}
})
</script>
</body>
- 单独配置组件的选项对象
<body>
<div id="app">
<my-com></my-com>
<my-com></my-com>
</div>
<script src="lib/vue.js"></script>
<script>
var MyCom = {
template:
`<div>
<h1> {{ title }} </h1>
</div>
`,
data () {
return {
title: '这是标题'
}
}
}
new Vue({
el: '#app',
data: {
value: 'test'
},
components: {
MyCom
}
})
</script>
</body>
组件通信
- 在组件间传递数据的操作,称为组件通信
父组件向子组件传值
<body>
<div id="app">
<my-com title = '静态属性'></my-com>
<!-- 获取父组件中的数据 -->
<my-com :title="item.title"></my-com>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component ('my-com', {
props: ['title'],
template:
`<div>
<h1> {{ title }} </h1>
</div>
`
})
new Vue({
el: '#app',
data: {
item: {
title: '父组件中的 title'
}
}
})
</script>
</body>
Props 命名规则
- 建议 prop 命名使用 camelCase,父组件绑定时使用 kebab-case
<body>
<div id="app">
<!-- 获取父组件中的数据 -->
<my-com :my-title="item.title"></my-com>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component ('my-com', {
props: ['myTitle'],
template:
`<div>
<h1> {{ myTitle }} </h1>
</div>
`
})
new Vue({
el: '#app',
data: {
item: {
title: '父组件中的 title'
}
}
})
</script>
</body>
单向数据流
- 父子组件间的所有 prop 都是单向下行绑定的
- 如果子组件要处理 prop 数据,应当存储在 data 中后操作
<body>
<div id="app">
<!-- 获取父组件中的数据 -->
<my-com :my-title="item.title"></my-com>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component ('my-com', {
props: ['myTitle'],
template:
`<div>
<h1> {{ parentTitle }} </h1>
</div>
`,
data () {
return {
parentTitle: this.myTitle.toUpperCase()
}
}
})
new Vue({
el: '#app',
data: {
item: {
title: 'parentTitle'
}
}
})
</script>
</body>
⚠️:如果 prop 为数组或对象时,子组件操作将会影响到父组 件的状态
<body>
<div id="app">
<!-- 获取父组件中的数据 -->
<my-com
:my-obj="item.obj"
:my-content="item.content"></my-com>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component ('my-com', {
props: ['MyObj', 'MyContent'],
template:
`<div>
<h1> {{ content }} </h1>
<button @click="fn">按钮</button>
</div>
`,
data () {
return {
content: this.MyContent
}
},
methods: {
fn () {
// 会改变父组件中的obj
this.MyObj.name = 'oy'
this.content = '新的content'
}
}
})
new Vue({
el: '#app',
data: {
item: {
content: '这是父组件的内容',
obj: {
name: 'zm',
title: '父组件的title'
}
}
}
})
</script>
</body>
Props 类型
- Prop 可以设置类型检查,这时需要将 props 更改为一个带有验 证需求的对象,并指定对应类型
- prop 还可以同时指定多个类型,通过数组方式保存即可
<body>
<div id="app">
<!-- 获取父组件中的数据 -->
<my-com
:my-name="item.name"
:my-age="item.age"
:my-sex="item.sex"></my-com>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component ('my-com', {
props: {
MyName: String,
MyAge: Number,
MySex: [String, Number]
},
template:
`<div>
<p> {{ MyName }} </p>
<p> {{ MyAge }} </p>
<p> {{ MySex }} </p>
</div>
`
})
new Vue({
el: '#app',
data: {
item: {
name: 'zm',
age: 18,
sex: 1
}
}
})
</script>
</body>
Props 验证
- 当 prop 需要设置多种规则时,可以将 prop 的值设置为选项对象
- 之前的类型检测功能通过 type 选项设置
- required 用于设置数据为必填项
- default 用于给可选项指定默认值,当父组件未传递数据时生效
- 注意:当默认值为数组或对象时,必须为工厂函数返回的形式
- validator 用于给传入的 prop 设置校验函数,return 值为 false 时 Vue.js 会发出警告
<body>
<div id="app">
<!-- 获取父组件中的数据 -->
<my-com
:parse-string="item.name"
:parse-number="item.age"
:parse-boolean="item.female"
:parse-arr="item.arr"
:parse-obj="item.obj"></my-com>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component ('my-com', {
props: {
ParseString: {
type: String,
default: 'abc',
validator: function (value) {
return value.startsWith('www')
}
},
ParseNumber: {
type: Number,
require: true
},
ParseBoolean: Boolean,
ParseArr: {
default: function () {
return [1]
}
},
ParseObj: {
default: function () {
return {price: 0.1}
}
}
},
template:
`<div>
<p> {{ ParseString }} </p>
<p> {{ ParseNumber }} </p>
<p> {{ ParseBoolean }} </p>
<p> {{ ParseArr }} </p>
<p> {{ ParseObj }} </p>
</div>
`
})
new Vue({
el: '#app',
data: {
item: {
name: 'www.baidu.com',
age: 18,
female: true,
// arr: [1, 2],
// obj: {
// price: 1
// }
}
}
})
</script>
</body>
- ⚠️:验证函数中无法使用实例的 data、methods 等功能
非 Props 属性
- 当父组件给子组件设置了属性,但此属性在 props 中不存在,这 时会自动绑定到子组件的根元素上
- 如果组件根元素已经存在了对应属性,则会替换组件内部的值
- class 与 style 是例外,当内外都设置时,属性会自动合并
- 如果不希望继承父组件设置的属性,可以设置 inheritAttrs: false,但只适用于普通属性,class 与 style 不受影响
<body>
<div id="app">
<!-- 获取父组件中的数据 -->
<my-com title="这是非props属性" class="非props的class" style="width: 200px;" index="1"></my-com>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('my-com', {
inheritAttrs: false,
template:
`<div style="height: 200px" class="这是子组件的class" title="这是子组件的title">
看看非 props 属性
</div>
`
})
new Vue({
el: '#app',
data: {
}
})
</script>
</body>
子组件向父组件传值
- 子向父传值需要通过自定义事件实现
<body>
<div id="app">
<product-num
v-for="product in products"
:key="product.id"
:title="product.title"
:count="product.count"
@count-change="onCountChange"></product-num>
<!-- 父组件监听自定义事件时,需要接收子组件传递的数据 -->
<!-- @count-change="totalCount+=$emit"> -->
<p>{{ totalCount }}</p>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('product-num', {
props: ['title'],
data () {
return {
count: 0
}
},
methods: {
countIns1 () {
this.count++,
// 1、子组件数据发生变化后,触发自定义事件
// 2、触发自定义事件时,可以向父组件传值
this.$emit('count-change', 1)
},
countIns5 () {
this.count += 5,
this.$emit('count-change', 5)
}
},
template:
`<div>
{{ title }}的个数为{{ count }}
<button @click="countIns1">+1</button>
<button @click="countIns5">+5</button>
</div>
`
})
new Vue({
el: '#app',
data: {
products: [
{
id: 0,
title: 'apple'
},
{
id: 1,
title: 'peach'
}
],
totalCount: 0
},
methods: {
onCountChange (val) {
this.totalCount += val
}
}
})
</script>
</body>
组件与 v-model
- v-model 用于组件时,需要通过 props 与自定义事件实现
<body>
<div id="app">
<p>{{ iptVal }}</p>
<ipt-com v-model="iptVal"></ipt-com>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('ipt-com', {
props: ['value'],
data() {
return {
count: 0
}
},
methods: {
onInput(event) {
this.$emit('input', event.target.value)
}
},
template:
` <input
type="text"
:value="value"
@input="onInput">
`
})
new Vue({
el: '#app',
data: {
iptVal: '78'
}
})
</script>
</body>
非父子组件传值
- 非父子组件指的是兄弟组件或完全无关的两个组件
兄弟组件传值
- 兄弟组件可以通过父组件进行数据中转
<body>
<div id="app">
<!-- 接收组件 A 中的 value -->
<comp-a @on-recieve="value = $event"></comp-a>
<div>父组件的value: {{ value }}</div>
<!-- 向组件 B 传值 value -->
<comp-b
:value="value"></comp-b>
</div>
<script src="lib/vue.js"></script>
<script>
// 组件 A 给父组件传值
Vue.component('comp-a', {
data () {
return {
value: 'A'
}
},
template:
`<div>
组件A中的value为: {{ value }}
<button @click="$emit('on-recieve', value)">按钮</button>
</div>
`
})
// 组件 B 接收父组件传值
Vue.component('comp-b', {
props: ['value'],
template:
`<div>
组件B中的value为: {{ value }}
</div>
`
})
new Vue({
el: '#app',
data: {
value: ''
}
})
</script>
</body>
EventBus
- EventBus (事件总线)是一个独立的事件中心,用于管理不同组 件间的传值操作
- EventBus 通过一个新的 Vue 实例来管理组件传值操作,组件通过给实例注册事件、调用事件来实现数据传递
- 发送数据的组件触发 bus 事件,接收的组件给 bus 注册对应事件
- 给 bus 注册对应事件通过 $on() 操作
<body>
<div id="app">
<component-item></component-item>
<component-total></component-total>
</div>
<script src="lib/vue.js"></script>
<script src="EventBus.js"></script>
<script>
// 组件 A 传值,触发事件
Vue.component('component-item', {
data () {
return {
count: 0
}
},
template:
`<div>
苹果的数量: {{ count }}
<button @click="fn">+1</button>
</div>
`,
methods: {
fn (event) {
bus.$emit('total-change', 1),
this.count++
}
}
})
// 组件 B 接收值,注册事件
Vue.component('component-total', {
data() {
return {
totalCount: 0
}
},
template:
`<div>
水果的总数为: {{ totalCount }}
</div>
`,
created () {
bus.$on ('total-change', (num) => {
this.totalCount += num
})
}
})
new Vue({
el: '#app',
data: {
}
})
</script>
</body>
其他通信方式
$root
- $root 用于访问当前组件树根实例,设置简单的 Vue 应用时可以 通过此方式进行组件传值
// 组件 A 获取父组件的值
Vue.component('comp-a', {
template:
`<div>
父组件中的 value 为: {{ $root.value }}
<button @click=fn>+1</button>
</div>
`,
methods: {
fn () {
this.$root.count++
}
}
})
- 除了 $root , Vue.js 中还提供了 $parent 与 $children 用于 便捷访问父子组件
$refs
- $refs 用于获取设置了 ref 属性的 HTML 标签或子组件
- 给普通 HTML 标签设置 ref 属性,$refs 可以获取 DOM 对象
<body>
<div id="app">
<input type="text" ref="ipt">
<button @click="fn">点击</button>
</div>
<script src="lib/vue.js"></script>
<script>
// 根实例
new Vue({
el: '#app',
data: {
count: 0
},
methods: {
fn () {
this.$refs.ipt.focus()
}
}
})
</script>
</body>
- 给子组件设置 ref 属性,渲染后可通过 $refs 获取子组件实例
<body>
<div id="app">
<com-a ref="comA"></com-a>
</div>
<script src="lib/vue.js"></script>
<script>
// 子组件
var ComA = Vue.component('com-a', {
data () {
return {
value: 123
}
},
template:
`<div>
子组件中的 value 为: {{ value }}
</div>
`
})
// 根实例
new Vue({
el: '#app',
data: {
},
components: {
ComA
},
mounted () {
this.$refs.comA.value = 456
}
})
</script>
</body>
组件插槽
- 组件插槽可以便捷的设置组件内容
单个插槽
- 平常我们书写的组件,组件首尾标签中书写的内容会被抛弃
<comp-a>haha</comp-a>
- 我们需要通过 <slot> 进行插槽设置
template:
`<div>
父组件中的 value 为: {{ count }}
<slot></slot>
</div>
`
- 需要注意模板内容的渲染位置
<comp-a>
<!-- 这里只能访问父组件的内容 -->
父组件中的value: {{ parentValue }}
<span>xixi</span>
</comp-a>
- 我们可以在 <slot> 中为插槽设置默认值
template:
`<div>
子组件中的value: {{ value }}
<slot>这是默认内容</slot>
</d
具名插槽
- 如果组件中有多个位置需要设置插槽,据需要给 设置 name,称为具名插槽
<comp-a>
<template v-slot:header>
<h1>标题</h1>
</template>
<template v-slot:default>
<p>内容</p>
</template>
<template v-slot:footer>
<p>底部</p>
</template>
</comp-a>
template:
`<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`
- 没有名字的组件可以不用 template 包裹
- v-slot 可以简写为#
<comp-a>
<template #header>
<h1>标题</h1>
</template>
<p>内容1</p>
<p>内容2</p>
<template #footer>
<p>底部</p>
</template>
</comp-a>
作用域插槽
- 用于让插槽可以使用子组件的数据
- 组件将需要被插槽使用的数据通过 v-bind 绑定给 <slot>,这种 用于给插槽传递数据的属性称为插槽 prop
Vue.component('comp-a', {
data () {
return {
value: 'love'
}
},
template:
`<div>
<main>
<slot :value="value">这是默认内容</slot>
</main>
</div>
`
})
- 组件绑定数据后,插槽中需要通过 v-slot 接收数据
<comp-a>
<template v-slot:default="objData">
<p>{{ objData.value }}</p>
</template>
</comp-a>
- 如果只存在默认插槽,同时又需要接收数据,可以进行简写
<comp-a v-slot:default="objData">
<p>{{ objData.value }}</p>
</comp-a>
<comp-a v-slot="objData">
<p>{{ objData.value }}</p>
</comp-a>
- 还可以通过 ES6 的解构操作进行数据接收
<comp-a v-slot="{ value }">
<p>{{ value }}</p>
</comp-a>
内置组件
动态组件
- 动态组件适用于多个组件频繁切换的处理
- <component> 用于将一个‘元组件’渲染为动态组件,以 is 属 性值决定渲染哪个组件
<component :is="currentCom"></component>
- 用于实现多个组件的快速切换,例如选项卡效果
// 组件 A、B、C
var ComA = {template:`<div>这是组件A</div>`}
var ComB = {template:`<div>这是组件B</div>`}
var ComC = {template:`<div>这是组件C</div>`}
// 根实例
new Vue({
el: '#app',
data: {
titles: ['ComA','ComB','ComC'],
currentCom: 'ComA',
},
components: {ComA, ComB, ComC}
})
<div id="app">
<button
v-for="title in titles"
:key="title"
@click="currentCom = title">
{{ title }}
</button>
<component :is="currentCom"></component>
</div>
- is 属性会在每次切换组件时,Vue 都会创建一个新的组件实例
keep-alive 组件
- 主要用于保留组件状态或避免组件重新渲染
<keep-alive>
<component :is="currentCom"></component>
</keep-alive>
- include 属性用于指定哪些组件会被缓存,具有多种设置方式
<keep-alive include="ComA,ComB,ComC">
<component :is="currentCom"></component>
</keep-alive>
<keep-alive include="['ComA','ComB','ComC']">
<component :is="currentCom"></component>
</keep-alive>
<keep-alive include="/Com[ABC]/">
<component :is="currentCom"></component>
</keep-alive>
- exclude 属性用于指定哪些组件不会被缓存
<keep-alive exclude="ComC">
<component :is="currentCom"></component>
</keep-alive>
- max 属性用于设置最大缓存个数
<keep-alive max="2">
<component :is="currentCom"></component>
</keep-alive>
过渡组件
- 用于在 Vue 插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡、动画效果
transition 组件
1、用于给元素和组件添加进入/离开过渡
- 条件渲染 (使用 v-if )
- 条件展示 (使用 v-show )
2、组件提供了 6个 class,用于设置过渡的具体效果
进入的类名:
- v-enter
- v-enter-to
- v-enter-active
离开的类名:
- v-leave
- v-leave-to
- v-leave-active
<style>
/* 进入初始状态 */
.v-enter {
opacity: 0
}
/* 进入过程 */
.v-enter-active {
transition: all, 1s;
}
/* 进入的最终状态 */
.v-enter-to {
opacity: 0.5;
}
/* 离开过程 */
.v-leave-active {
transition: opacity, 1s;
}
/* 离开最终状态 */
.v-leave-to {
opacity: 0
}
</style>
<div id="app">
<button @click="show = !show">切换</button>
<transition>
<p v-if="show">hello world</p>
</transition>
</div>
<script src="lib/vue.js"></script>
<script>
// 根实例
new Vue({
el: '#app',
data: {
show: true
}
})
</script>
transition 组件-相关属性
- 给组件设置 name 属性,可用于给多个元素、组件设置不同的过 渡效果,这时需要将 v- 更改为对应 name- 的形式
- 通过 appear 属性,可以让组件在初始渲染时实现过渡
自定义过渡类名
- 自定义类名比普通类名优先级更高,在使用第三方 CSS 动画库时非常有用
1、用于设置自定义过渡类名的属性如下:
- enter-class
- enter-active-class
- enter-to-class
- leave-class
- leave-active-class
- leave-to-class
<style>
/* 进入过程、离开过程 */
.test {
transition: all, 0.5s;
}
</style>
<div id="app">
<div>
<button @click="show = !show">切换</button>
<transition enter-active-class="test" leave-active-class="test">
<p v-if="show">自定义过渡类名</p>
</transition>
</div>
</div>
// 根实例
new Vue({
el: '#app',
data: {
show: true
}
})
2、用于设置初始过渡类名的属性如下:
- appear-class
- appear-to-class
- appear-active-class
3、Animate.css 是一个第三方 CSS 动画库,通过设置类名来给元素 添加各种动画效果
<div>
<button @click="show = !show">切换</button>
<!-- 通过自定义过渡类名设置,给组件添加第三方动画库的类名效果 -->
<transition enter-active-class="animate__bounceInDown" leave-active-class="animate__bounceOutDown">
<!-- 必须给要使用动画的元素设置基础类名 animate__animated -->
<p
v-if="show"
class="animated">animated 动画库</p>
</transition>
</div>
</div>
4、使用注意:
- animate__ 前缀与 compat 版本
- 基础类名 animated
transition-group 组件
1、 用于给列表统一设置过渡动画
- tag 属性用于设置容器元素,默认为 <span>
- 过渡会应用于内部元素,而不是容器
- 子节点必须有独立的 key,动画才能正常工作
<transition-group
tag="ul"
>
<li
v-for="item in items"
:key="item.id"
@click="removeItem(item)"
>
{{ item.title }}
</li>
</transition-group>
- 当列表元素变更导致元素位移,可以通过 .v-move 类名设置移动 时的效果
.v-move {
transition: all .5s;
}