- 组件用于封装页面的部分功能,将功能的结构,样式,逻辑代码封装为整体
- 提高功能的复用性和可维护性,更好的专注于业务逻辑
- 组件使用时为自定义HTML标签形式,通过组件名作为自定义标签名
<div id="app">
<!-- 普通HTML标签 -->
<p>p标签内容</p>
<!-- Vue.js组件 -->
<my-com></my-com>
</div>
组件注册
全局注册
- 全局注册的组件在注册之后可以用于任意实例或者组件中
Vue.component('组件名',{/*选项对象*/});
- 注意:全局注册必须设置在根Vue实例创建之前
例子:
<body>
<div id="app">
<p>这是p标签</p>
<my-component></my-component>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('my-component',{
template : '<div>这是我们全局注册的组件</div>'
});
// 根实例
new Vue({
el: '#app',
data: {
}
})
</script>
</body>
组件基础
- 本质上,组件是可以复用的Vue实例,可以与new Vue接收相同的选项, 比如data,methods,生命周期钩子等等
- 仅有的例外是像el这样根实例特有的选项
组件命名规则
- 组件具有两种命名规则:
- kebab-case:'my-component'(烤串命名法,短横线链接多个单词的命名方式)
-
PascalCase:'MyComponent'(传统命名法,帕斯卡命名法,每部分内容的首字母大写)
- 注意:无论采用什么类型的命名方式,在DOM中都只有kebab-case可以使用
template 选项
-
template 选项用于设置组建的结构,最终被引入根实例或者其他组件中
-
注意:组件必须只有一个根元素(一个根元素,比如最外层的div就一对)
data 选项
- data 选项用于存储组件的数据,与根实例不同,组件的data 选项必须为函数,数据设置在返回值对象中
- 这种实现方式是为了确保每个组件实例可以维护一份被返回对象的独立的拷贝,不会相互影响。
局部注册
-
局部注册的组件只能用在当前实例或组件中
-
单独配置组件的选项对象:
-
ES6的对象属性简写:
组件通信
- 父组件向子组件传值
- 子组件向父组件传值
- 非父子组件传值
- 其他通信方式
父组件向子组件传值
- 通过子组件的props选项接收父组件的传值
注意:props不要和data存在同名的属性,否则会覆盖 -
父组件设置方式如下:
<body>
<div id="app">
<!-- 静态属性设置 -->
<my-component-a
title="这是静态的标题"
content="这是静态的内容"
></my-component-a>
<!-- 动态属性绑定 -->
<my-component-a
v-bind:title="'这是静态的标题,这是演示'"
:content="'这是静态内容'"
></my-component-a>
<!-- 动态属性绑定:常用操作 -->
<my-component-a
:title="item.title"
:content="item.content"
></my-component-a>
</div>
<script src="lib/vue.js"></script>
<script>
// 创建子组件
Vue.component('my-component-a',{
props : ['title','content'],
template : `
<div>
<h3>{{ title }}</h3>
<p>{{ content }}</p>
</div>
`
});
new Vue({
el: '#app',
data: {
item: {
title: '这是示例标题',
content: '这是示例内容'
}
}
});
</script>
</body>
Props命名规则
-
建议prop命名使用camelCase,父组件绑定时使用kebab-case
<body>
<div id="app">
<my-component
:item-title="item.title"
:item-content="item.content"
></my-component>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('my-component', {
props: ['itemTitle', 'itemContent'],
template: `
<div>
<h3>{{ itemTitle }}</h3>
<p>{{ itemContent }}</p>
</div>
`
})
new Vue({
el: '#app',
data: {
item: {
title: '这是示例标题',
content: '这是示例内容'
}
}
})
</script>
</body>
props练习
- 通过v-for创建组件
<body>
<div id="app">
<!-- 通过 v-for 遍历数据 items,创建组件并生成内容 -->
<demo-item
v-for="item in items"
:item-title="item.title"
:item-content="item.content"
:key="item.id"
:item="item"
></demo-item>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('demoItem', {
props: ['itemTitle', 'itemContent', 'item'],
template: `
<div>
<h3>{{ itemTitle }}</h3>
<p> {{ itemContent }} </p>
</div>
`
})
new Vue({
el: '#app',
data: {
// 准备给子组件使用的数据
items: [
{
id : 1,
title: '示例标题1',
content: '示例内容1'
},
{
id : 2,
title: '示例标题2',
content: '示例内容2'
},
{
id : 3,
title: '示例标题3',
content: '示例内容3'
},
]
}
})
</script>
</body>
单向数据流
- 父子组件间的所有prop都是单向下行绑定的,不能反向影响父组件
-
如果子组件要处理prop数据,应当存储在data中再操作
- 如果prop为数组或者对象的时候,子组件操作将会影响到父组件的状态
<body>
<div id="app">
<my-component
:initial-title="title"
:obj="obj"
:arr = 'arr'
></my-component>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('my-component', {
props: ['initialTitle', 'obj','arr'],
template: `
<div>
{{ title }}
{{ obj }}
{{ arr }}
<button @click="fn">按钮</button>
</div>
`,
data () {
return {
title: this.initialTitle
}
},
methods: {
fn () {
// title只是个字符串,不会反向影响到父组件的状态
this.title = '这是新的标题';
// this.initialTitle = '这是新的标题'; // 不会影响父组件
// obj是对象,会反向影响到父组件
this.obj.name = 'jack';
this.obj.age = '20';
// arr是数组,也会反向影响到父组件
this.arr[0] = 4;
}
}
});
new Vue({
el: '#app',
data: {
title: '这是示例标题',
obj: {
name: 'william',
age: 18
},
arr : [1,2,3]
}
});
</script>
</body>
props类型
- Prop 可以设置类型检查,这时需要将 props 更改为一个带有验证需求的对象,并指定对应类型。
- prop还可以同时指定多个类型,通过数组方式保存就可以了
设置这个内容检测仅仅是检测,如果忽略这个错误也是可以渲染内容上去的,所以设置之后要看控制台检查错误
props验证
- 当 prop 需要设置多种规则时,可以将 prop 的值设置为选项对象
-
之前的类型检测功能通过 type 选项设置
-
required 用于设置数据为必填项
-
default 用于给可选项指定默认值,当父组件未传递数据时生效
-
当默认值是数组或者对象的时候,必须为工厂函数返回的形式,避免引用类型在多个组件中产生相互影响的情况
- validator 用于给传入的 prop 设置校验函数,return 值为false 时 Vue.js 会发出警告
验证函数中没法使用实例的methods,data等功能
非props属性
-
当父组件给子组件设置了属性,此属性在props不存在,这时就会自动绑定到子组件的根元素上
- 如果组件根元素已经存在了对应属性,则会替换组件内部的值
-
class 与 style 是例外,当内外都设置时,属性会自动合并
-
如果不希望继承父组件设置的属性,可以设置inheritAttrs:false,但只适用于普通属性,class 与 style 不受影响
子组件向父组件传值
子组件传向父组件需要通过自定义事件来实现
-
举个例子:商品为子组件,购物车为父组件,父组件需要统计商品个数,就需要在子组件个数变化时传值给父组件
-
子组件数据变化时,通过$emit()触发自定义事件
- 事件的名称建议使用kebab-case命名方式
-
父组件监听子组件的自定义事件,设置好处理程序
自定义事件传值
-
子组件触发事件时可以向父组件传值
-
父组件在监听事件时需要接收子组件传递的数据
-
也可以写成自定义事件,对应处理的程序
组件与v-model
-
v-model用于组件时,需要通过props与自定义事件实现
实践案例:
<body>
<div id="app">
<p>输入框内容: {{ iptValue }}</p>
<com-input v-model = 'iptValue'></com-input>
</div>
<script src="lib/vue.js"></script>
<script>
var comInput = {
props : ['value'],
template : `
<input
type="text"
:value="value"
@input = "onInput"
>
`
,
methods : {
onInput(event){
this.$emit('input',event.target.value)
}
}
}
new Vue({
el: '#app',
data: {
iptValue : ''
},
components : {
comInput
}
})
</script>
</body>
非父子组件传值
什么是非父子组件?兄弟组件或者完全无关的两个组件
兄弟组件传值
兄弟组件之间是可以通过父组件进行数据中转的
<body>
<div id="app">
<!-- 父组件接收子组件A的数据 -->
<com-a
@value-change="value = $event"
></com-a>
<!-- 父组件将数据传递给子组件B -->
<com-b
:value="value"
></com-b>
</div>
<script src="lib/vue.js"></script>
<script>
// 子组件A:发送数据
Vue.component('ComA', {
template: `
<div>
组件A的内容: {{ value }}
<button
@click="$emit('value-change', value)"
>发送</button>
</div>
`,
data () {
return {
value: '这是组件A中的数据'
}
}
});
// 子组件B:接收数据
Vue.component('ComB', {
props: ['value'],
template: `
<div>
组件B接收到: {{ value }}
</div>
`
});
// 根实例(父组件)
new Vue({
el: '#app',
data: {
// 用于数据中转
value: ''
}
})
</script>
</body>
EventBus
- 当组件嵌套关系复杂的时候,根据组件关系传值会比较繁琐
- 如果组件为了数据中转,data中会存在许多与当前组件功能无关的数据
- EventBus (事件总线)是一个独立的事件中心,用于管理不同组件间的传值操作
- EventBus 通过一个新的 Vue 实例来管理组件传值操作,组件通过给实例注册事件、调用事件来实现数据传递
// 将新的Vue实例bus存放在一个叫做EventBus.js的文件中
var bus = new Vue();
-
发送数据的组件触发bus事件,接收的组件给bus注册对应的事件
-
给 bus 注册对应事件通过 $on() 操作
最后创建根实例执行代码即可
<body>
<div id="app">
<h3>购物车</h3>
<product-item></product-item>
<product-total></product-total>
</div>
<script src="lib/vue.js"></script>
<!-- 引入EventBus.js要在vue.js引入之后 -->
<script src="EventBus.js"></script>
<script>
// 商品组件
Vue.component('ProductItem', {
template: `
<div>
<span>商品名称:苹果,商品个数:{{ count }}</span>
<button
@click="countIns"
>+1</button>
</div>
`,
data () {
return {
count: 0
}
},
methods: {
countIns () {
// 给bus触发自定义事件,传递数据
bus.$emit('countChange', 1);
this.count++;
}
}
});
// 计数组件
Vue.component('ProductTotal', {
template: `
<p>总个数为: {{ totalCount }}</p>
`,
data () {
return {
totalCount: 0
}
},
created () {
// 给 bus 注册事件,并接收数据
bus.$on('countChange', (productCount) => {
// 实例创建完毕,可以使用 data 等功能
// 使用箭头函数,直接使用this
this.totalCount += productCount;
});
}
})
// 根实例
new Vue({
el: '#app',
data: {
}
});
</script>
</body>
其他通信方式
$root
-
$root 用于访问当前组件树根实例,设置简单的Vue应用时可以通过此方式 进行组件传值
进一步证明是根实例的证据:(组件B是子组件A的子组件)
<body>
<div id="app">
<com-a></com-a>
</div>
<script src="lib/vue.js"></script>
<script>
// 根实例的子组件A的子组件B
var ComB = {
template: `
<div>
组件B: {{ $root.count }}
<button @click="clickFn">按钮</button>
</div>
`,
methods: {
clickFn () {
this.$root.count = 200;
}
}
};
// 子组件A
var ComA = {
template: `
<div>
组件A: {{ $root.count }}
<button @click="clickFn">按钮</button>
<com-b></com-b>
</div>
`,
methods: {
clickFn () {
this.$root.count = 100;
}
},
components: {
ComB
}
};
// 根实例
new Vue({
el: '#app',
data: {
count: 0
},
components: {
ComA
}
});
</script>
</body>
- 除了
$root
, Vue.js 中还提供了$parent
与$children
用于 便捷访问父子组件
$refs
-
$refs
用于获取设置了ref属性的HTML标签或者子组件 - 给普通HTML标签设置ref属性,
$refs
可以获取DOM对象
- 给子组件设置ref属性,渲染后可以通过
$refs
获取子组件实例
组件插槽
便捷的设置组件内容
单个插槽
-
如果我们希望组件标签可以像 HTML 标签一样设置内容,那么组件的使用灵活度会很高
-
但平常我们书写的组件,组件首尾标签中书写的内容会被抛弃
-
这个时候我们需要通过 <slot> 进行插槽设置
-
需要注意模板内容的渲染位置
-
我们可以在 <slot> 中为插槽设置默认值,也称为后备内容
具名插槽
-
如果组件中有多个位置需要设置插槽,据需要给 <slot> 设置name,称为具名插槽
default可以省略掉
简写模式:
<body>
<div id="app">
<com-a>
<template v-slot:header>
<h3>组件的头部内容</h3>
</template>
<!-- <template v-slot:default>
<p>组件的主体内容1</p>
<p>组件的主体内容2</p>
</template> -->
<p>组件的主体内容1</p>
<p>组件的主体内容2</p>
<template #footer>
<p>组件底部内容</p>
</template>
</com-a>
</div>
<script src="lib/vue.js"></script>
<script>
// 子组件
Vue.component('ComA', {
template: `
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`
});
new Vue({
el: '#app',
data: {
}
});
</script>
</body>
作用域插槽
用于让插槽可以使用子组件的数据
-
组件将需要被插槽使用的数据通过 v-bind 绑定给 <slot>,这种用于给插槽传递数据的属性称为插槽 prop
-
组件绑定数据后,插槽中需要通过 v-slot 接收数据
-
如果只存在默认插槽,同时又需要接收数据,可以进行简写
<!-- 只具有默认插槽的作用域插槽书写方式 -->
<!-- <com-b v-slot="dataObj"> -->
<com-b #default="dataObj">
{{ dataObj }}
</com-b>
<!-- 通过 ES6 的解构操作接收作用域插槽的数据 -->
<com-b v-slot="{ value, num }">
{{ value }}
{{ num }}
</com-b>
内置组件
动态组件
动态组件适用于多个组件频繁切换的处理
-
<component> 用于将一个‘元组件’渲染为动态组件,以 is 属性值决定渲染哪个组件
- 可以用来实现多个组件的快速切换,比如说选项卡效果
<body>
<div id="app">
<!-- 按钮代表选项卡的标题功能 -->
<button
v-for="title in titles"
:key="title"
@click="currentCom = title"
>
{{ title }}
</button>
<!-- component 设置动态组件 -->
<component :is="currentCom"></component>
</div>
<script src="lib/vue.js"></script>
<script>
// 设置要切换的子组件选项对象
var ComA = {
template: `<p>这是组件A的内容:<input type="text"></p>`
};
var ComB = {
template: `<p>这是组件B的内容:<input type="text"></p>`
};
var ComC = {
template: `<p>这是组件C的内容:<input type="text"></p>`
};
new Vue({
el: '#app',
data: {
// 所有组件名称
titles: ['ComA', 'ComB', 'ComC'],
// 当前组件名称
currentCom: 'ComA'
},
components: {
ComA, ComB, ComC
}
});
</script>
</body>
is 属性会在每次切换组件时,Vue 都会创建一个新的组件实例意思就是销毁了再创建,无法进行保留
keep-alive组件
主要用于保留组件状态或避免组件重新渲染
-
include 属性用于指定哪些组件会被缓存,具有多种设置方式
-
exclude 属性用于指定哪些组件不会被缓存
-
max属性用于设置最大的缓存数
过渡组件
用于在Vue插入,更新,或者移除DOM的时候,提供多种不同方式的应用过渡,动画效果
transition组件
- 用于给元素和组件添加进入/离开过渡
- 条件渲染(v-if)
- 条件展示(使用v-show)
- 动态组件
- 组件根节点
- 组件提供了6个class,用于设置过渡的具体效果
- 进入的类名:
- v-enter
- v-enter-to
- v-enter-active
- 离开的类名
- v-leave
- v-leave-to
- v-leave-active
<style>
/* 用于设置出场的最终状态 */
.v-leave-to {
opacity: 0;
}
/* 用于设置过渡的执行过程 */
.v-leave-active {
transition: opacity 1s;
}
/* 用于设置入场的初始状态 */
.v-enter {
opacity: 0;
}
/* 用于设置入场的最终状态 */
.v-enter-to {
opacity: 0.5;
}
/* 用于设置入场的过程 */
.v-enter-active {
transition: all 1s;
}
</style>
<body>
<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>
</body>
transition组件
-
给组件设置name属性,可以给多个元素,组件设置不同的过渡效果,需要将v-更改为name-的形式
比如:
<transition name="demo"> 的对应类名前缀为:- demo-enter
- demo-leave....等等
-
通过 appear 属性,可以让组件在初始渲染时实现过渡
自定义过渡类名
自定义类名比普通类名优先级更高,在使用第三方 CSS 动画库时非常有用
- 用于设置自定义过渡类名的属性如下
- enter-class
- enter-active-class
- enter-to-class
- leave-class
- leave-active-class
- leave-to-class
- 用于设置初始过渡类名的属性如下
- appear-class
- appear-to-class
- appear-active-class
<style>
.v-enter, .v-leave-to {
opacity: 0;
}
.v-enter-active, .v-leave-active {
transition: all .5s;
}
/* 自定义过渡效果优先级更高 */
.test {
transition: all 3s;
}
</style>
</head>
<body>
<div id="app">
<button @click="show = !show">切换</button>
<transition
enter-active-class="test"
leave-active-class="test"
>
<p v-if="show">这是 p 标签</p>
</transition>
</div>
<script src="./lib/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
show: true
}
});
</script>
</body>
Animate.css 是一个第三方 CSS 动画库,通过设置类名来给元素添加各种动画效果
使用注意
- animate_ 前缀与compat版本
- 基础类名animated
使用animate.css的一个简单案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
/>
<!-- 不需要添加 animate__ 的兼容版本,但是官方建议使用完整版本 -->
<!-- "https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.0.0/animate.compat.css" -->
</head>
<body>
<div id="app">
<button @click="show = !show">按钮</button>
<!-- 通过自定义过渡类名设置,给组件添加第三方动画库的类名效果 -->
<transition
enter-active-class="animate__bounceInDown"
leave-active-class="animate__bounceOutDown"
>
<!-- 必须给要使用动画的元素设置基础类名 animate__animated -->
<p
v-if="show"
class="animate__animated"
>hello world</p>
</transition>
</div>
<script src="lib/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
show: true
}
});
</script>
</body>
</html>
transition-group组件
- <transition-group> 用于给列表统一设置过渡动画
- tag属性用于设置容器元素,默认为<span>
- 过渡会应用于内部元素,而不是容器
- 子节点必须要有独立的key,动画才能正常工作
- 当列表元素变更导致元素位移,可以使用
.v-move
类名设置移动时的效果
列表移除添加小案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
ul {
position: relative;
}
.v-enter, .v-leave-to {
opacity: 0;
transform: translateX(100px);
}
.v-enter-active, .v-leave-active {
transition: all .5s;
}
/* 让元素在离场的过程中脱离标准流 */
.v-leave-active {
position: absolute;
}
.v-move {
transition: all .5s;
}
</style>
</head>
<body>
<div id="app">
<input type="text"
v-model="newTitle"
@keyup.enter="addItem"
>
<transition-group
tag="ul"
>
<li
v-for="item in items"
:key="item.id"
@click="removeItem(item)"
>
{{ item.title }}
</li>
</transition-group>
</div>
<script src="./lib/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
items: [
{ id: 1, title: '示例内容1'},
{ id: 2, title: '示例内容2'},
{ id: 3, title: '示例内容3'},
{ id: 4, title: '示例内容4'},
{ id: 5, title: '示例内容5'},
],
newTitle: '',
latestId: 5
},
methods: {
addItem () {
this.items.push({
id: this.latestId + 1,
title: this.newTitle
});
this.latestId++;
this.newTitle = '';
},
removeItem (item) {
var i = this.items.indexOf(item);
this.items.splice(i, 1);
}
}
});
</script>
</body>
</html>