笔记来源:拉勾教育 - 大前端就业集训营
文章内容:学习过程中的笔记、感悟、和经验
Vue组件
Vue组件
组件用于封装页面的部分功能,将功能的结构、样式、逻辑代码封装为一个整体
一旦发生问题我们只需要去指定组件维护即可,提高复用性和可维护性
使用方法:以自定义HTML的形式插入,使用组件名作为自定义标签名
<body>
<div id="app">
<p>我是普通标签</p>
<!-- 自定义组件直接使用组件名作为标签名即可 -->
<new-zujian></new-zujian>
</div>
</body>
组件注册
全局注册组件
全局注册的组件可以应用于任何vue实例中,通过调用component()方法创建
书写方法:vue.component( ' 组件名 ' ,{ 选项对象 } )
注意:全局注册组件必须在根vue实例创建之前使用
<body>
<div id="app">
<p>我是普通标签</p>
<!-- 自定义组件直接使用组件名作为标签名即可 -->
<new-component></new-component>
</div>
<script>
// 注册全局组件
Vue.component('new-component', {
// 组件内部自定义标签内容
template: '<div>我是全局注册的一个组件</div>'
})
// vue实例
const vm = new Vue({
el: '#app',
data: {},
})
</script>
</body>
局部注册组件
局部注册组件只能在当前实例或者组件中使用,在vue实例的components中使用
<body>
<div id="app">
<!-- 引入组件 -->
<new-component-a></new-component-a>
<new-component-b></new-component-b>
<new-component-c></new-component-c>
</div>
<script>
// 我们可以把组件写在外面,直接在components中使用即可
const NewComponentB = {
template: `<div>{{value}}</div>`,
data() {
return {
value: '我是组件的data2'
}
}
}
const vm = new Vue({
el: '#app',
data: {},
// 注册局部组件,内部可以设置多个组件
components: {
// 使用-连接需要使用引号包裹起来,首字母大写不需要使用引号包裹
'new-component-a': {
template: `<div>{{value}}</div>`,
data() {
return {
value: '我是组件的data'
}
}
},
// 我们可以直接使用外部配置好的组件直接引入进来
'new-component-c': NewComponentB, //这种写法兼容性较好
// 可以使用ES6语法简写为下面(属性名和变量名相同可以简写)
NewComponentB
}
})
</script>
</body>
组件基础
本质上,组件就是一个可以复用的Vue实例,所以内部也可以设置data、methods、生命周期等钩子函数(el除外)
组件命名规则
- kebab-case(最常用):使用-连接前后名称,例如:
my-son - PascalCase:每部分内容首字母大写,例如:
MySon
注意:无论组件以何种方式命名,在Dom中引入的的时候必须使用kebab-case命名方法,因为html不区分大小写,全部都认为是小写
template选项
用于设置组件结构,最终会被引入根实例或者其他组件中,内部和在html中书写的结构一样,也可以书写差值表达式、指令等等
<body>
<div id="app">
<p>我是普通标签</p>
<!-- 自定义组件直接使用组件名作为标签名即可 -->
<new-component></new-component>
</div>
<script>
// 注册全局组件
Vue.component('new-component', {
// 使用template设置组件结构,内部可以使用差值表达式
template: `
<div>
<p>{{1 + 2 * 3}}</p>
</div>
`
})
// vue实例
const vm = new Vue({
el: '#app',
data: {
value: '我是一条数据'
},
})
</script>
</body>
注意:template选项内部只能有一个大盒子
data选项
用于存储组件中的数据,与根实例不同,data选项是一个函数,数据设置在返回值中,其他方面和根实例使用方法相同
<body>
<div id="app">
<!-- 引入组件 -->
<new-component></new-component>
</div>
<script>
// 注册全局组件
Vue.component('new-component', {
// 使用template设置组件结构,内部可以使用差值表达式
// 如果差值表达式需要使用数据,就需要在组件的data中设置
template: `
<div>
<p>{{value}}</p>
</div>
`,
// data必须写成一个函数,函数返回值才是我们需要的data
// 这里我们使用es6语法
data() {
return {
value: '我是组件的data'
}
}
})
// vue实例
const vm = new Vue({
el: '#app',
data: {},
})
</script>
</body>
注意事项
之所以要以函数的方式存在,是为了确保每一个组件实例都有自己独立一份data数据,不会互相影响
组件间通讯
子组件和父组件以及更加复杂组件关系之间数据的传递
父组件向子组件传值
下面是子组件获取Vue实例中的数据
<body>
<div id="app">
<!-- 引入子组件 -->
<!-- 在子组件内部使用自定义属性的方式引入数据,组件内容可以是静态内容、绑定的静态内容、动态获取data数据 -->
<new-component title="静态内容"> </new-component>
<new-component :title="'静态内容'"> </new-component>
<!-- 这里使用item.title获取到vue实例中的数据 -->
<new-component :title="item.title"></new-component>
</div>
<script>
// vue实例
const vm = new Vue({
el: '#app',
data: {
item: {
title: '我是title'
}
},
// 局部注册组件
components: {
'new-component': {
// 子组件内部使用props接收数据
props: ['title'],
// 在结构中使用数据可以使用差值表达式
template: `<div>{{title}}</div>`
}
}
})
</script>
</body>
注意props里面的属性不要和data存在同名属性
props命名规则
建议prop命名使用驼峰命名法,父组件绑定时使用 - 连接
<body>
<div id="app">
<!-- 传递数据可以使用 - 连接 -->
<new-component :my-title="item.title"></new-component>
</div>
<script>
// vue实例
const vm = new Vue({
el: '#app',
data: {
item: {
title: '我是title'
}
},
// 局部组件
components: {
'new-component': {
// 子组件内部使用props接收数据
// prop建议使用驼峰命名法
props: ['myTitle'],
// 在结构中使用数据可以使用差值表达式
template: `<div>{{myTitle}}</div>`
}
}
})
</script>
</body>
练习:通过v-for创建组件
<body>
<div id="app">
<!-- 使用template模版进行v-for遍历 -->
<template v-for="item in items">
<!-- 内部使用组件,组件内部使用两个数据 -->
<new-component :my-title="item.title" :my-content="item.content"></new-component>
</template>
<!-- 直接在组件上使用v-for,这时需要设置:key,这里我把整个item全部传入组件,之后再使用 -->
<new-component v-for="item in items" :key="item.title" :item="item" :my-title="item.title"
:my-content="item.content"></new-component>
</div>
<script>
// vue实例
const vm = new Vue({
el: '#app',
data: {
// 定义一组数据,后面v-for遍历
items: [
{title: '标题1',content: '内容1'},
{title: '标题2',content: '内容2'},
{title: '标题3',content: '内容3'},
{title: '标题4',content: '内容4'},
]
},
// 局部组件
components: {
// 创建组件
'new-component': {
// 组件接收数据
props: ['myTitle', 'myContent', 'item'],
// 组件使用数据
template: `<div><h3>{{myTitle}}</h3><p>{{myContent}}</p><p>{{item}}</p></div>`
}
}
})
</script>
</body>
注意:这里的key是无法传进子组件的,vue知道我们的:key是用于标识数据的,不会将key传进组件中
单向数据流
父子组件间的所有prop都是单向向下绑定的,传向子组件的数据是不能反过来影响父组件数据的,从子组件更改数据不会影响父组件
因为props的数据是不能更改的,如果子组件要处理porp数据,应该存储在子组件的data中进行操作或者在计算属性中操作
<body>
<div id="app">
<!-- 使用数据 -->
<p>{{content}}</p>
<!-- 引入子组件,子组件传入vue-content数据 -->
<new-component :vue-content="content"></new-component>
</div>
<script>
// vue实例
const vm = new Vue({
el: '#app',
data: {
content: '内容'
},
// 局部组件
components: {
// 创建组件
'new-component': {
// 组件接收vueContent
props: ['vueContent'],
// 组件中使用data数据
template: `<div>{{content}}</div>`,
data () {
return {
// 把接受来的数据存在data中,就可以给组件使用了,后期我们就可以编辑这个值了
content: this.vueContent
}
}
}
}
})
</script>
<!-- vue中是绝对不允许直接更改props数据的,更改了也无法影响父组件(数组对象除外) -->
</body>
注意:如果prop值是数组和对象,在子组件中编辑将会影响到父组件中数据,因为数组和对象都是引用地址,一旦更改全部引用都会更改
props类型检测
默认props获取到的数据是没有类型限制的,字符串、数组、对象都可以,可以设置props类型检查
将props更改为一个带有验证需求的对象(这个对象就是构造函数,例如String、Array等),并指定对应类型,可以限制类型
<body>
<div id="app">
<!-- 引入子组件,子组件传入四个数据 -->
<new-component
:one="one"
:tow="tow"
:three="three"
:four="four"
></new-component>
</div>
<script>
// vue实例
const vm = new Vue({
el: '#app',
data: {
// 书写四个数据
one: {name:'zs',age:19}, //我们把第一条设置为错误类型,可以发现控制台弹出一个错误信息
tow: [4,5,6],
three: {name:'zs',age:19},
four: {name:'ls',age:20}
},
// 局部组件
components: {
// 创建组件
'new-component': {
// 把props设置为对象形式!!!!!!!!!!!!!!!!!!
props: {
// 每个数据后面添加一个构造函数类型
one: String, //要求数据类型为字符串
tow: Array, //要求数据类型为数组
three: null, //要求数据类型任意,写为undefined也可以
four: Object //要求数据类型为对象
},
// 使用数据
template: `
<div>
<p>我是第1行{{one}}</p>
<p>我是第2行{{tow}}</p>
<p>我是第3行{{three}}</p>
<p>我是第4行{{four}}</p>
</div>`
}
}
})
</script>
</body>
注意事项
- 当我们给定的值不是props要求的值,vue只会给出我们一个警告(但还是可以渲染),提示类型错误,因为这不属于语法错误,仍然可以渲染
- 如果想对一条数据指定多个类型,把指定的prop设置为数组,数组里面添加类型即可,例如
one: [String , Array ]
props验证
当prop需要设置多种规则的时候,可以把prop写成对象格式,内部为配置项,常用配置项包括:
- type:同上类型检测
- required:默认flase,设置数据为必填项,父组件必须传递这个值,否则报错并且在vue devtools中发现有这个值,值为unf
- default:给数据设置默认值,当父组件未传递值的时候生效,一般情况下,required和default不会同时存在在一个数据配置中,当默认值为数组或者对象的时候,需要像data一样使用函数返回,否则会报错,但也能渲染
- validator:给传入的prop设置校验函数,return值为flase时vue会发出警告,但也能渲染
// 组件
Vue.component('new-component',{
// 父组件传入数据使用对象格式
props: {
one: {
// type - 用来对传入的数据进行类型检测
type: [Array, String]
},
tow: {
type: null,
// required - 设置必传项,不传则报错
required: true,
},
three: {
type: null,
// default设置默认值,父元素不传入使用默认值
default: '我是默认值'
},
four: {
type: Object,
// 当默认值为数组或者对象需要使用函数返回
default() {
return {name: 'zs',age: 19}
}
},
five: {
type: null,
// 对传入的值进行校验,返回结果为flase会发出警告
validator(data){
return data === 'lagou'
}
}
},
template: '<div>{{one}}{{tow}}{{three}}{{four}}{{five}}</div>'
})
注意:props中的数据无法使用组件中的data、methods等内容,因为props是在组件实例创建之前的,无法调用组件中的数据和方法,这里的this指向window
非props属性
当父组件给子组件传递数据的时候设置属性,此属性在props中不存在,这时这个属性就会绑定在子组件的跟元素上
<!-- 给子组件绑定一个他根本不需要的属性,属性会绑定到生成的结构根元素中 -->
<new-component class="box" name="my" style="height: 100px;"></new-component>
注意事项
- 如果子组件中绑定了值,而非props属性又绑定一次会发生覆盖(class和style不会覆盖而会发生合并)
- 如果不希望继承属性,可以在组件选项中设置
inheritAttrs:flase,但只适用于普通属性,class和style不受影响
// 组件
Vue.component('new-component',{
// 设置可以拒绝继承属性,但class和style还是会继承
inheritAttrs: false,
props: {},
template: '<div></div>'
})
子组件向父组件传值
子组件向父组件传值需要使用自定义事件实现
当子组件发生数据变化时,通过$emit()触发自定义事件,事件名称建议使用 - 连接,事件写在html结构中,事件名就是自定义事件名
购物车案例
需求:使用数据在购物车创建商品,商品内部数量变更影响外部数据总数量
<body>
<!-- 挂载元素 -->
<div id="app">
<!-- 插入组件
@add-total自定义事件:子组件触发
v-for循环:遍历商品列表,创建商品
:name向组件传递数据
-->
<shopping-cart
@add-total="addTotal"
v-for="good in goods"
:key="good.id"
:name="good.name">
</shopping-cart>
<!-- 商品数量合计 -->
<p>合计商品数量:{{total}}</p>
</div>
<script>
// 创建全局组件
Vue.component('shopping-cart',{
// 传递过来的数据
props: ['name'],
// 结构
// 使用传递过来的数据,使用data数据表示数量
// 添加按钮,添加点击事件更改数据
template: `
<div>
<span>商品名称:{{name}},商品数量{{count}}</span>
<button @click="addCount()">+1</button>
</div>`,
// 组件data数据
data () {
return {
count: 0
}
},
// 函数
methods: {
// 用于修改商品数量
addCount(){
// 商品数量自增
this.count++
// 通知父元素调用事件
this.$emit('add-total')
}
}
})
// Vue实例
new Vue({
// 挂载对象
el: '#app',
// 数据列表
data: {
// 商品列表
goods: [
{id: 1, name: '苹果'},
{id: 2, name: '香蕉'},
{id: 3, name: '橙子'}
],
// 商品总数量
total: 0
},
// 函数
methods: {
// 增加商品数量
addTotal(){
// 商品总数量自增
this.total++
}
}
})
//this.$emit('add-total')会调用<shopping-cart>上面的@add-total事件,@add-total事件调用会调用Vue实例上的addTotal方法,实现总数量的增加
</script>
</body>
自定义事件传值(改造购物车)
子组件向父组件传值可以直接使用emit()的第二个参数执行
改造上面的案例
// 修改组件模版,再添加一个按钮,按钮上同样创建点击事件,两个按钮出发的时候带不同参数实现不同的结果
template: `
<div>
<span>商品名称:{{name}},商品数量{{count}}</span>
<button @click="addCount(1)">+1</button>
<button @click="addCount(5)">+5</button>
</div>`,
// 修改组件中的事件函数,让函数可以接收一个参数val
addCount(val){
// 使用参数增加相应个数
this.count += val
// 通知父元素调用事件,并且通过$emit输出一个参数,后面想要使用这个参数可以使用 $event
this.$emit('add-total',val)
}
// 修改父组件中的事件函数,并让函数接受一个参数val
// 增加商品数量
addTotal(val){
// 商品总数加上参数
this.total += val
}
// 如果我们@add-total="total += $event"
注意:当我们想要使用$emit传递过来的参数的时候,有两种方式
- 如果直接把事件函数体写在html行内,使用参数直接使用event"`
- 如果我们事件调用的是vue实例中的函数 ,那么这个参数直接传入函数的参数即可,例如
addTotal(*val*)
完整购物车代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/vue.js"></script>
</head>
<body>
<!-- 挂载元素 -->
<div id="app">
<!-- 插入组件
@add-total自定义事件:子组件触发
v-for循环:遍历商品列表,创建商品
:name向组件传递数据
-->
<shopping-cart
@add-total="addTotal"
v-for="good in goods"
:key="good.id"
:name="good.name">
</shopping-cart>
<!-- 商品数量合计 -->
<p>合计商品数量:{{total}}</p>
</div>
<script>
// 创建全局组件
Vue.component('shopping-cart',{
// 传递过来的数据
props: ['name'],
// 结构
// 使用传递过来的数据,使用data数据表示数量
// 添加按钮,添加点击事件更改数据,时间带一个参数,通过控制不同的参数实现加不同的值
template: `
<div>
<button @click="addCount(1)">加1件</button>
<button @click="addCount(5)">加5件</button>
<span>商品名称:{{name}},商品数量{{count}}</span>
<button @click="addCount(-1)">减1件</button>
<button @click="addCount(-5)">减5件</button>
</div>`,
// 组件data数据
data () {
return {
count: 0
}
},
// 函数
methods: {
// 用于修改商品数量
addCount(val){
// 商品数量自增
this.count += val
// 通知父元素调用事件
this.$emit('add-total',val)
}
}
})
// Vue实例
new Vue({
// 挂载对象
el: '#app',
// 数据列表
data: {
// 商品列表
goods: [
{id: 1, name: '苹果'},
{id: 2, name: '香蕉'},
{id: 3, name: '橙子'}
],
// 商品总数量
total: 0
},
// 函数
methods: {
// 增加商品数量
addTotal(val){
// 商品总数量自增
this.total += val
}
}
})
//this.$emit('add-total')会调用<shopping-cart>上面的@add-total事件,@add-total事件调用会调用Vue实例上的addTotal方法,实现总数量的增加
</script>
</body>
</html>
组件和v-model
v-model用于组件时,需要通过props与自定义事件实现
<body>
<!-- 挂载元素 -->
<div id="app">
<!-- 实时展示数据 -->
<p>{{value}}</p>
<!-- 插入模版
使用v-model双向绑定数据,v-model会自动把值传递给组件中,组件中使用props接收即可
-->
<new-component v-model="value"></new-component>
</div>
<script>
// 创建全局组件
Vue.component('new-component',{
// props接收数据,接收数据存在value中
props: ['value'],
// 结构模版
// 使用:value="value"绑定v-model传递过来的数据
// @input="onInput"绑定input的input事件
template: `
<div>
<input
type="text"
@input="onInput"
:value="value">
</div>`,
methods: {
// 绑定input标签的input事件,注意这里需要传入一个参数,这参数就是事件元素本身
onInput(event){
// 这里的input绑定的是子组件的input事件,而不是input标签的input事件
// 后面第二个参数是input标签中的value值
this.$emit('input',event.target.value)
}
}
})
// Vue实例
new Vue({
el: '#app',
data: {
value: '123'
}
})
</script>
</body>
注意事项
- 代码中演示的事件函数写在methods中,如果想卸载template里面需要写成
@input="$emit('input',$event.target.value)",可以省略this,直接使用$event表示事件对象
逻辑梳理
- 组件通过v-model把数据传递给组件
- 组件中使用props接收数据value
- 组件模版添加input把value绑定给value
- 再给input标签绑定一个input事件,事件中绑定整个组件的input事件
- 事件的第二个参数把input标签的value导出出去
非父子组件传值
非父子组件一共有两种形式
- 兄弟组件:同一个父组件下的两个同级组件
- 完全无关的两个组件
兄弟组件间传值
兄弟组件之间传值需要通过父组件进行中转,使用子组件改变父组件中的值,然后其他组件再绑定这个值
<body>
<!-- 挂载元素 -->
<div id="app">
<p>value的值:{{value}}</p>
<son1 @chage-value="value = $event"></son1>
<son2 :value="value"></son2>
</div>
<script>
const son1= {
template: `
<div>
<button @click="$emit('chage-value','我是新的值,我要被传出去')">给我变</button>
</div>
`
}
const son2= {
props: ['value'],
template: `<div>组件2接收的值:{{value}}</div> `
}
// Vue实例
new Vue({
el: '#app',
data: {
value: '我是旧值,我会被替换掉'
},
components: {
son1,
son2
}
})
</script>
</body>
代码逻辑解析:
- 在vue实例中添加数据value
- 组件1中添加按钮通过自定义事件改编父组件的value值
- 组件2绑定父组件的value值
EventBus - 事件总线
目前的问题
- 当组件存在嵌套关系时,如果嵌套层数过多按照关系传值会很复杂
- 组件为了数据中转,data中会存在许多与当前组件功能无关的数据
EventBus (事件总线)是一个独立的事件中心,用于管理不同组件间的传值操作,只是一项功能,不可以存储数据
EventBus通过一个新的vue实例来管理组件传值操作,组件通过给实例注册事件,调用事件来实现数据传递
功能实现:发送数据的组件触发bus事件,接收组件给bus注册对应事件,一般情况下,我们会吧bus的实例创建放在一个单独的js文件中使用js引入
实例:son1向son2传递数据
<body>
<div id="app">
<!-- 插入两个组件 -->
<son1></son1>
<son2></son2>
</div>
<script>
// 创建两个组件
// 组件1
const son1 = {
// 结构中添加一个按钮,按钮点击触发事件
template: `<div><button @click="clickBtn">传递数据</button></div>`,
methods: {
// 按钮点击触发的事件
clickBtn(event){
// 调用把bus中的chage-value事件,并把数据传递过去
bus.$emit('chage-value','新数据')
}
}
}
// 组件2
const son2= {
// 组件2直接使用自己的data数据
template: `<div>{{value}}</div>`,
data () {
return {
value: '原始'
}
},
// /当组件2创建之后在bus上注册一个事件,当事件触发把数据拿过来(value)使用
created () {
// 使用bus的$on方法,添加'chage-value自定义事件,事件接受一个参数value
bus.$on('chage-value',(value)=>{
// 使用接收的数据替换现有的数据
// 注意:这里的this因为使用了箭头函数,所以this就指向son2本身,如果使用function需要手动设置this
this.value = value
})
}
}
const bus = new Vue()
const vm = new Vue({
el: '#app',
// 添加局部组件
components: {
son1,son2
}
})
</script>
</body>
代码逻辑解析
- 接收数据的组件在初始化完成后在bus上注册一个事件
bus.$on(事件名,事件函数(接收的数据)),一旦事件触发,会把数据传递过来 - 发送数据的组件在自己的函数中调用bus的事件
bus.$emit(事件名,要发送的数据)传递数据 - 当发送数据的组件发送数据之后,接收方也能拿到数据,就可以使用了
原理上来说这种方式适合任意两个组件的传值,这里只是使用兄弟组件做演示
其他通信方式
日常工作中,进行数据通信使用的大部分都是之前学写到的通信方式,以下方式作为了解,因为仅仅是用于简单的vue项目
$root
用来访问当前组件树(组件嵌套结构中的顶层标签)的根实例,如果没有父组件,访问自己,简单的vue应用可以通过这种方式传值
书写方式:this.$root.数据名......
<body>
<div id="app">
<p>one的值是{{one}}</p>
<!-- 插入组件 -->
<son1></son1>
</div>
<script>
// 组件
const son1 = {
// 结构中添加一个按钮,按钮点击触发事件
template: `<div><button @click="clickBtn">+1</button></div>`,
methods: {
clickBtn(){
// 使用this.$root直接访问组件树的根实例data数据
this.$root.one += 1
}
}
}
const vm = new Vue({
el: '#app',
data :{one: 1},
// 添加局部组件
components: {son1}
})
</script>
</body>
如果我们工作中这样写,一旦更改超过我们的预期,我们可能找不到具体是哪个$root更改的数据,所以不建议使用
补充
除了parent(父组件)和$children(子组件)用于快速访问父子组件,具体使用方法可查询官方文档
$refs
获取设置了ref属性的Html标签或者子组件
// 简单用法
<body>
<div id="app">
<!-- 给input设置ref属性 -->
<input type="text" ref="inp">
<!-- 按钮设置点击事件 -->
<button @click="fn">获取焦点</button>
</div>
<script>
const vm = new Vue({
el: '#app',
data :{one: 1},
methods: {
fn(){
// 使用this.$refs获取指定ref属性的元素
this.$refs.inp.focus()
}
}
})
</script>
</body>
给子组件设置ref属性,渲染后可以通过$refs获取子组件实例
<body>
<div id="app">
<!-- 插入组件,设置组件的ref属性 -->
<son1 ref="son1"></son1>
<!-- 按钮设置点击事件 -->
<button @click="fn">修改为新值</button>
</div>
<script>
const vm = new Vue({
el: '#app',
methods: {
// 按钮是件函数
fn(){
// 直接通过$refs.son1访问组件son1并修改data中的value值
this.$refs.son1.value = '新的值'
}
},
// /组件
components: {
son1: {
// 模版中使用data中的value
template: `<div>{{value}}</div>`,
data () {
return {
value: '旧的值'
}
}
}
}
})
</script>
</body>
注意:
- 要想通过ref访问组件,要确保组件已经加载完毕
- 所有设置过ref的元素都会存在vue实例的$refs属性中,以键值对的方式存储起来
组件插槽
组件插槽可以快捷设置组件内容
单个插槽
如果我们希望组件标签可以像HTML标签一样设置内容,那么组件的灵活度会很高,但是默认情况下,直接在组件html中书写的内容会被抛弃
可以在组件中使用<slot>进行插槽设置
<body>
<div id="app">
<!-- 插入组件 -->
<son1>
<!-- 注意:在这里只能获取到父组件的内容,无法读取son1里面的data内容 -->
<!-- 在插槽内可以使用普通文本,也可通过{{}}动态获取内容 -->
我是一个普通文本内容
<p>{{slot[0]}}</p>
<p>{{slot[1]}}</p>
<p>{{slot[2]}}</p>
</son1>
<!-- 再插入一个组件,组件内插槽不设置任何内容 -->
<!-- 如果这里不设置任何内容,插槽设置的默认内容会出现在这里 -->
<son1></son1>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
// vue实例数据
slot: ['插槽内容','插槽内容2','插槽内容3']
},
// 组件
components: {
son1: {
// html模版中添加一个插槽,插槽设置默认内容
// 默认内容会在没有设置插槽内容是自动显示,反之插槽设置了内容会被替换
template: `
<div>
<p>组件的固定内容</p>
<slot>我是插槽默认内容,如果你没有在外边设置插槽内容,就会显示我</slot>
</div>
`
}
}
})
</script>
</body>
注意事项:
使用插槽直接书写的{{内容}}只能引入父组件的数据,无法访问子组件的数据
具名插槽
如果组件多个位置需要设置插槽,按需要给slot设置name,称为具名插槽
<body>
<div id="app">
<!-- 插入组件 -->
<son1>
<!-- 使用具名插槽,具名插槽使用的时候需要在template标签中使用v-slot:指定要使用的插槽名
template表示占位符,没有实际意义
v-slot:插槽名 要写在template中 -->
<template v-slot:header>
<p>我是header插槽内容</p>
<p>我是header插槽内容2</p>
</template>
<!-- 如果我们插槽没有命名,那么直接写结构就可以了,如果想要写结构,可以写成下面注释的内容 -->
<!-- <template v-slot:default> -->
<p>我是main插槽内容</p>
<p>我是main插槽内容2</p>
<!-- </template> -->
<!-- 具名插槽使用的时候可以吧v-slot:简写为# -->
<template #footer>
<p>我是footer插槽内容</p>
<p>我是footer插槽内容2</p>
</template>
</son1>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {},
// 组件
components: {
son1: {
// html模版中添加一个插槽,给不同的插槽设置不同的名字,就可以插入多个插槽
template: `
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`
}
}
})
</script>
</body>
具名插槽使用的时候vue允许把v-slot:简写为#
作用域插槽
如果我们希望插槽内部也可以使用子组件的data数据需要使用作用域插槽
组件将需要被使用的数据通过v-bind绑定给slot标签,这种用于插槽传递数据的属性称作插槽prop
<body>
<div id="app">
<!-- 插入组件 -->
<son1>
<!-- 使用作用域插槽
#default用于绑定插槽名,default是默认插槽
#default="obj"表示将默认插槽所有使用v-bind绑定的值组成一个对象
后面我们使用.调用属性名的方式调用数据-->
<template #default="obj">
<p>{{obj.value}}</p>
<p>{{obj.one}}</p>
<p>{{obj}}</p>
</template>
</son1>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {},
// 组件
components: {
son1: {
// 模版哪使用v-bind方式调用data数据
template: `
<div>
<slot :value="value" :one="one"></slot>
</div>
`,
data () {
return {
value: '子组件value',
one: '子组件的one'
}
}
}
}
})
</script>
</body>
其他写法
-
如果只存在默认插槽,还想传递数据,那么可以直接把v-slot写在组件上,例如:
<组件名 v-slot:default="对象名"></组件名>,甚至可以直接省略:default,注意这里不可以直接写成#="对象名" -
可以直接使用ES6的解构操作进行数据接收,例如
v-slot:default="{ value ,one..... }",使用的时候就可以直接使用value和one了-
<template #default="{value,one}"> <p>{{value}}</p> <p>{{one}}</p> </template>
-
内置组件
vue内部提供的组件,例如slot
动态组件
适用于多个组件频换切换的处理
使用<component>将一个元组件渲染为动态组件,使用is属性绑定需要渲染的组件名
<body>
<div id="app">
<!-- 创建三个按钮用于切换data中的how数据
当我们点击三个按钮的时候动态组件会分别显示A、B、C组件-->
<button v-for="name in title" :key="name" @click="how = name">{{name}}</button>
<!-- 动态组件,绑定is属性到how -->
<component :is="how"></component>
</div>
<script>
// 三个组件
const comA = {
template: `<div>comA组件的内容</div>`
},
comB = {
template: `<div>comB组件的内容</div>`
},
comC = {
template: `<div>comC组件的内容</div>`
}
const vm = new Vue({
el: '#app',
data: {
// 组件名数组
title: ['comA', 'comB', 'comC'],
// 动态组件当前绑定的组件名
how: 'comA'
},
// 组件
components: {
comA,
comB,
comC
}
})
</script>
</body>
is属性在每次切换的时候,vue都会先销毁当前组件再创建一个新的组件实例,前一个组件的状态是无法保留的
keep-alive组件
主要用于保持组件状态或者避免重新渲染,程序会在组件切换过程中将组件进行缓存
书写方法:使用kepp-alive标签包裹动态组件即可
<div id="app">
<!-- 创建三个按钮用于切换data中的how数据
当我们点击三个按钮的时候动态组件会分别显示A、B、C组件-->
<button v-for="name in title" :key="name" @click="how = name">{{name}}</button>
<!-- 设置keep-alive组件,让每次切换组件都能保留住之前的状态 -->
<keep-alive>
<component :is="how"></component>
</keep-alive>
</div>
keep-alive常用属性
-
include:用于指定那些组件会被缓存
-
<!-- 三种include设置方式 方式1:直接设置组件名,使用逗号隔开(注意逗号后面不能加空格) 方式2:使用动态绑定,支持数组 方式3:使用正则表达式 下面三条都设置了comA,comB,comC缓存 --> <keep-alive include="comA,comB,comC"> </keep-alive> <keep-alive :include="数组"> </keep-alive> <keep-alive :include="/com[ABC]/"> </keep-alive>
-
-
exclude:用于指定哪些组件不会被缓存(使用方法同上)
-
max:用于设置最大缓存个数(缓存距离当前操作最近的n次切换操作)
-
<!-- 设置最大缓存只能缓存两个组件,缓存离当前操作组件最近的2次操作组件,超过2个最早的组件删除缓存 --> <keep-alive max="2">
-
过渡组件
用于vue插入、更新、移除Dom时,提供应用过度、动画效果
transition组件
给元素或者组件添加进入、离开过渡效果:
- 条件渲染v-if
- 条件展示v-show
- 动态组件
- 组件根节点
组件提供了6个class类名,可以在css中设置具体过渡效果:
- 入场动画
- v-enter:入场前的样式(动画过渡还没开始)
- v-enter-to:入场后的样式(此时动画过渡已经完成)(一般不设置,使用当前状态)
- v-enter-active:元素入场过程参数控制(入场方式、过渡时间等等)
- 离场动画
- v-leave:离场前的样式(动画过渡还没开始)(一般不设置,使用当前状态)
- v-leave-to:离场后的样式(此时动画过渡已经完成)
- v-leave-active:元素离场过程参数控制(离场方式、过渡时间等等)
!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/vue.js"></script>
<style>
/* 直接使用类名即可,这是vue提供的类名 */
/* 设置进场前和离场后样式 */
.v-enter,
.v-leave-to {
opacity: 0;
}
/* 配置进场离场过渡方式 */
.v-enter-active,
.v-leave-active {
/* 全部属性 事件0.5秒 */
transition: all .5s;
}
</style>
</head>
<body>
<div id="app">
<!-- 按钮点击切换now的值 -->
<button @click="now = !now">切换</button>
<!-- 过渡组件 -->
<transition>
<!-- 组件内p标签使用v-show -->
<p v-show="now">内容</p>
</transition>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
now: true
}
})
</script>
</body>
</html>
一般情况下,入场最终状态和出场处置状态不需要设置,就直接使用原始状态就可以了
相关属性
name属性
用于给多个元素、组件设置不同的过渡效果,这是需要讲v-更改为对应的name-形式,没有设置name的还是使用v-
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/vue.js"></script>
<style>
/* 设置第一个p标签的入场前和出场动画后样式 */
/* 这里使用name名字one代替v */
.one-enter,
.one-leave-to {
opacity: 0;
}
/* 设置第二个p标签的入场前和出场动画后样式 */
/* 这里使用name名字tow代替v */
.tow-enter,
.tow-leave-to {
font-size: 1000px;
}
/* 配置两个p标签的进场离场过渡方式 */
.one-enter-active,
.one-leave-active,
.tow-enter-active,
.tow-leave-active {
/* 全部属性 事件0.5秒 */
transition: all .5s;
}
</style>
</head>
<body>
<div id="app">
<!-- 按钮点击切换now的值 -->
<button @click="now = !now">切换</button>
<!-- 两个过渡组件,设置不同的name名字 -->
<transition name="one">
<!-- 组件内p标签使用v-show -->
<p v-show="now">内容</p>
</transition>
<transition name="tow">
<!-- 组件内p标签使用v-show -->
<p v-show="now">内容</p>
</transition>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
now: true
}
})
</script>
</body>
appear属性
让组件在初始渲染的时候也能实现实现过渡效果,默认布尔值
<!-- 直接给transition组件加上appear属性即可 -->
<transition 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:入场配置
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/vue.js"></script>
<style>
/* 直接使用类名即可,这是vue提供的类名 */
/* 设置进场前和离场后样式 */
.v-enter,
.v-leave-to {
opacity: 0;
}
/* 配置进场离场过渡方式 */
.v-enter-active,
.v-leave-active {
/* 全部属性 事件0.5秒 */
transition: all .5s;
}
/* 另外再设置一个过渡类名 */
.test {
transition: all 5s;
}
</style>
</head>
<body>
<div id="app">
<button @click="now = !now">切换</button>
<!-- 过渡组件,过渡组件使用自定义过渡类名,就会使用自定义的过渡效果 -->
<transition enter-active-class="test" leave-active-class="test">
<p v-show="now">内容</p>
</transition>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
now: true
}
})
</script>
</body>
第三方css动画库 - Animate.css
通过设置类名给元素添加各种动画效果
Animate.css | A cross-browser library of CSS animations.
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<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" />
<script src="./js/vue.js"></script>
</head>
<body>
<div id="app">
<button @click="now = !now">切换</button>
<!-- 在transition中使用自定义过渡类名
animate__backInDown和animate__backOutDown可以在官网查找到 -->
<transition enter-active-class="animate__backInDown" leave-active-class="animate__backOutDown">
<!-- 这里使用新版本需要添加一个类名animate__animated -->
<p class="animate__animated" v-show="now">内容</p>
</transition>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
now: true
}
})
</script>
</body>
使用注意事项:
- animate__前缀和compat版本的问题:_:
- Animate.css目前最新版本(4.0+)要求需要过度的元素添加类名
animate__animated,而之前版本类名不需要animate__`前缀,如果你想使用新版本还不想添加这个前缀可以使用兼容compat版本
- Animate.css目前最新版本(4.0+)要求需要过度的元素添加类名
tarnsition-group组件
tarnsition-group用来给列表统一设置过渡效果
和tarnsition的区别
- tarnsition-group会生成一个元素节点容器,可以使用tag属性更改节点类型,默认是span节点
- 过渡效果会应用于内部的元素,而不会应用到容器上
- 子节点使用v-for必须设置独立的key,动画才能正常工作
tarnsition-group使用方法和tarnsition几乎相同
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/vue.js"></script>
<style>
/* css部分和tarnsition一摸一样 */
.v-enter,
.v-leave-to {
margin-left: -200px;
}
.v-enter-active,
.v-leave-active {
transition: all .5s;
}
</style>
</head>
<body>
<div id="app">
<!-- 两个按钮,点击可以修改num值 -->
<button @click="num++">+1行</button>
<button @click="num--">-1行</button>
<!-- transition-group组件
使用tag把默认的span修改为ul -->
<transition-group tag="ul">
<!-- 使用v-for创建li标签,注意要使用key属性 -->
<li v-for="(i,index) in num" :key="index">我是第{{i}}行</li>
</transition-group>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
num: 4
}
})
</script>
</body>
</html>
当列表元素因为变更导致后面元素位移比较生硬的时候,可以通过.v-move类名设置移动时的效果
<style>
ul {
position: relative;
}
.v-enter, .v-leave-to {
opacity: 0;
transform: translateX(100px);
}
.v-enter-active, .v-leave-active {
transition: all .5s;
}
/* 在元素离场初始状态的时候让元素在离场的过程中脱离标准流,因为在离场过程中如果不脱离标准流元素还是会占有位置,导致.v-move不能生效 */
.v-leave-active {
position: absolute;
}
/* 通过.v-move设置元素移动过渡效果 */
.v-move {
transition: all .5s;
}
</style>