组件化开发
组件用于封装页面的部分功能,将功能的结构、样式、逻辑代码封装起来,提高功能否对复用性和可维护性,更专注业务逻辑的开发。
组件注册
组件本质上时可复用的Vue实例,可以与new Vue接收相同的选项,例如data、methods以及生命周期钩子等。
命名规则:用 - 连接或者pascalCase命名法
调用规则:必须使用 - 连接,标签内部不区分大小写
全局注册
可以用于任意实例或组件中,必须创建于根Vue实例创建之前 Vue.component('组件名',{选项对象})
<div id="app">
<my-component></my-component>
</div>
<script>
Vue.component('my-component',{
template:<div>全局注册组件</div>
})
Vue.component('MyComB',{
template:
})
new Vue({
el:'#app',
data:{
}
})
</script>
组件基础
template选项
用于设置组件的结构。只能有一个根元素
<div id='#app'>
<my-complate></my-complate>
</div>
<script>
Vue.component('MyComplate',{ //全局注册组件
template:'<div>组件A的内容{{1+9*2}}</div>'
// template:'<div><p>组件A</p>/div>' //可以书写
// template:'<div>组件</div> <div></div>' //不可书写,只可使用一个根元素
})
new Vue({
el:'#app',
data:{}
})
</script>
data选项
数据绑定操作,只能将data中的数据放到方法中传入,为了使每次调用组件时不同调用位置实例相互独立。为实例设置作用域。
<div id='#app'>
<My-COmA><My-ComA>
</div>
<script>
Vue.component('MyComA',{
template:'<div>{{title}}</div>
<p>{{content}}</p>`
data() {
return {
title:'组件标题'
content:'组件内容'
}
}
})
</script>
局部注册
在实例化的Vue对象中添加components属性,将组件名以对象的方式添加到componsents属性中,data方法设置在components对象中以函数调用的形式书写
<div id = '#app'>
<my-com-a><my-com-a>
</div>
<script>
new Vue({
el:'app',
data:{}
components:{
'my-com-a':{
template:`<div><h3>{{title}}</h3><p>{{content}}</p></div>`,
data(){
return{title:'组件标题',
content:'组件内容'
}
}
}
}
})
</script>
可以将数据提取出来单独设置选项对象,结合ES6新语法设置对象属性和对象值相同书写一个即可
<div id = '#app'>
</div>
<script>
var MyComponentA = {
template: `
<div>
<h3>{{ title }}</h3>
<p>{{ content }}</p>
</div>
`,
data () {
return {
title: '组件 A 标题',
content: '组件 A 内容'
}
}
};
var componentB = ({
template:` <div>
<h3>{{ title }}</h3>
<p>{{ content }}</p>
</div>
`,
data () {
return {
title: '组件 B',
content: '组件 B 内容'
}
}
})
new Vue({
el:'#app'
data(){
}
comonents:{
'my-component-a':componentA,
compontentB
}
})
</script>
组件通信
父组件向子组件传参值
父组件给子组件传值过程中用到props属性,采用驼峰命名法 ,父组件绑定时使用 - 连接,标签中不区分大小写无法识别props。不要与data存在同名属性。引用data中的值时无需将data设置为方法
<div id="app">
<!-- 静态属性设置 -->
<my-component-a my-title="这是静态的标题" my-content="这是静态的内容"></my-component-a>
<!-- 动态属性绑定 -->
<my-component-a v-bind:my-title="'这是静态的标题,这是演示'" :my-content="'这是静态内容'"></my-component-a>
<!-- 动态属性绑定:常用操作 -->
<my-component-a :my-title="item.title":my-content="item.content" ></my-component-a>
</div>
<script>
// 创建子组件
Vue.component('my-component-a', {
props: ['myTitle', 'myContent'],
template: `
<div>
<h3>{{ myTitle }}</h3>
<p>{{ myContent }}</p>
</div>
`
});
new Vue({
el: '#app',
data: {
item: {
title: '这是示例标题',
content: '这是示例内容'
}
}
});
所有的prop属性都是单向下行绑定的。父组件可以传给子组件中时父组件的变化可以更新到子组件,但子组件的变化不会被父组件更新,如果子组件需要更改处理父组件传入的数据 ,应存到data中。父组件给子组件传入的时一个对象时,子组件更新后父组件也会被修改。父组件给子组件传值是使用prop方式的同时,还需要绑定要传输的数据
<div id="app">
<my-component
:initial-title="title"
:obj="obj"
></my-component>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('my-component', {
props: ['initialTitle', 'obj'],
template: `
<div>
{{ title }}
<button @click="fn">按钮</button>
</div>
`,
data () {
return {
title: this.initialTitle //将prop数据传入到data中才可双向绑定,但prop中的数据不会改变
}
},
methods: {
fn () {
// this.title = '这是新的标题';
// this.initialTitle = '这是新的标题'; // 不会影响父组件
this.obj.name = 'jack'; //父子组件中的值都会更改
}
}
});
new Vue({
el: '#app',
data: {
title: '这是示例标题',
obj: {
name: 'william',
age: 18
}
}
});
prop类型和验证
- 只有组件之间传递数据才需要props属性;
- 父组件向子组件传递的值可以进行验证,设置porp的type属性可以设置类型检查;require属性值为true可以进行必填项检查;defalut 用于给选项设置默认值(defalut属性和require属性不能同时设置到一个对象上)。给数组或者对象设置默认值时需要使用工厂函数返回的形式(如果直接书写对象再多个组件操作时可能会导致操作的是同一个数据相互影响);validator用于给传入的prop设置校验函数,检验具体规则,return为false是发出警告;如果检查出错不会中断输出,会在控制台警告。prop是在实例创建之前验证,无法使用实例的this关键字和method、data功能。
<div id="app">
<my-component
:par-str="str" :par-num="num" :par-arr="arr"
:par-obj="obj" :par-any="any" :par-data="str"
></my-component>
</div>
<script>
Vue.component('MyComponent', {
// 如果要设置 props 的具体规则,需要更改为对象写法
props: {
parStr:{
type:String,
default:'abc',
vaildator:function(val){
return value.startWith('aaa');//设置以aaa开头的字符串
}
},
parNum:{
type: Number,
require:true
},
parArr:{
type: Array,
default:function(){
return [1,2,2];
}
}
parObj: Object,
parAny: undefined, // null
parData: [String, Boolean] //设置多个类型
},
template: `
<div>
{{ parStr }} {{ parNum }} {{ parArr }}
{{ parObj }} {{ parAny }} {{ parData }}
</div> `
})
new Vue({
el: '#app',
data: {
num: 100,
str: 'aaabc',
arr: [1, 2, 3],
obj: {
content1: '示例内容1',
content2: '示例内容2'
},
any: [1, 2, 3]
}
});
</script>
非props属性
- 父组件给子组件传递的属性如果子组件没有prop接收,会自动绑定到子组件template内部的根元素上;
- 子组件可设置inheritAttrs:false阻止父组件传入的非prop属性,class和style会自动合并;
- 使用$attr批量将父组件传递的非prop属性(不含style和class)绑定到指定标签上需设置inheritAttrs:false;也可使用props逐个绑定
父组件给子组件传递参数
<myinput placehoder="请输入" data="list.data" class="myipt">
子组件逐个绑定
<div><input type='text' :placehoder="placehoder" :data="data" class='ipt'></div><!-->props:['placehoder','data']<-->
或attrs批量绑定
<div><input type='text' v-bind="$attrs" class='ipt'></div><!-->设置inheritAttrs:false<-->
$listeners注册事件
把父组件中DOM对象的原生事件(如focus,input等事件)绑定到子组件
父组件给子组件传递事件
<myinput placeholder="请输入" class="text" @focus="onFocus" @input="onInput" ></myinput>
子组件需要注册每个父组件绑定的事件
<div><input type="text" v-bind="$attrs" @focus="$emit('focus',$event)" @input="$emit('input',$event)"></div>
通过$listeners简化注册子组件中的事件
<div><input type="text" v-bind="$attrs" v-on="$listeners"></div>
子组件给父组件传值
子组件给父组件传值需要通过自定义事件实现。子组件修改时触发事件,父组件监听事件,通过emit触发自定义事件‘emit触发自定义事件`emit触发自定义事件‘emit(‘触发的事件名称,传参’),传来的参数在父组件通过$event接收。无需点击等手动操作,在父组件添加监听。自定义事件设置为kebab-case命名。将原生事件绑定到组件上时使用 v-on 的 .native 修饰符
@click.native=‘headlout’`
<div id="app">
<h3>购物车</h3>
<product-item
v-for="product in products"
:key="product.id"
:title="product.title"
@count-change="onCountChange" //每次触发$emit绑定的事件以后就执行一次函数
//@count-change="totalCount += $event" 也可使用接收到的事件参数相加。模板字符串中使用$字符vue会自动识别处理,自定义函数中必须传入参数才能执行
></product-item>
<p>商品总个数为:{{ totalCount }}</p> //父组件统计子组件商品个数
</div>
<script>
// 子组件
Vue.component('ProductItem', {
props: ['title'],
template: `
<div>
<span>商品名称: {{ title }}, 商品个数: {{ count }}</span>
<button @click="countIns1">+1</button>
<button @click="countIns5">+5</button>
</div>
`,
data() {return {count: 0}}, //子组件自己的data需要以返回值的形式传递
methods: {
countIns1 () {
this.$emit('count-change', 1); //通过$emit触发自定义事件,传入的第二个参数可以通过$event接收
this.count++;
},
countIns5 () {
this.$emit('count-change', 5); //$emit也可以触发基础事件
this.count += 5;
} }
});
// 父组件
new Vue({
el: '#app',
data: {
products: [ {
id: 1,
title: '苹果一斤'
},{
id: 2,
title: '香蕉一根'
},{ id: 3,
title: '橙子一个'
} ],
totalCount: 0
},
methods: {
onCountChange (productCount) { //定义触发事件之后的执行函数
this.totalCount += productCount;
}
}}
组件与v-model
v-model作为指令为数据双向绑定,直接绑定无法使用,需要props和自定义事件实现。
<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'], //接收的是value
template: `
<input
type="text"
:value="value"
@input="onInput" //可以书写到事件处理中,也可直接书写在标签中@input ="$emit('input',$event.target.value)"
> `,
methods: {
onInput (event) {
this.$emit('input', event.target.value)
}
} }
// 根实例
new Vue({
el: '#app',
data: {
iptValue: ''
},
components: {
ComInput
} });
</script>
非父子组件传值
非父子组件是指兄弟组件和兄弟组件的子组件
兄弟组件传值
方法1. 利用兄弟父组件进行数据中转
<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>
方法二、
EventBus是独立的事件中心,管理不同组件的传值操作,通过新的Vue实例管理组件传值操作,组件通过给实例注册事件、调用事件来实现数据传递。一般将EventBus利用单独的js文件存储
cerete是生命周期函数,表示是当前组件实例创建完毕触发。由于要使用totalCount,所以必须等加载完才可处理
<div id="app">
<h3>购物车</h3>
<product-item></product-item>
<product-total></product-total>
</div>
<script src="lib/vue.js"></script>
<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.totalCount += productCount;
});
}
})
// 根实例
new Vue({
el: '#app',
data: {
}
});
其他通信方式(一般不使用)
$root
用于访问当前组件树根实例,设置简单的Vue应用时使用
parent和parent 和parent和children同样可以访问父子组件,访问子组件时是一个实例数组
var ComB = {
template: `
<div>
组件B: {{ $root.count }}
<button @click="clickFn">按钮</button>
</div>
`,
methods: {
clickFn () {
this.$root.count = 200; //直接修改root组件的值
}
}
};
$refs
用于获取设置了ref属性的Html标签或子组件。给HTML标签设置ref属性时,"ref"可以获取DOM对象。设置ref属性时写在标签的内部‘<inputtype="text"ref="inp">‘使用时在Vue实例中通过this.ref"可以获取DOM对象。设置ref属性时写在标签的内部` <input type="text" ref="inp">` 使用时在Vue实例 中通过this.ref"可以获取DOM对象。设置ref属性时写在标签的内部‘<inputtype="text"ref="inp">‘使用时在Vue实例中通过this.refs.操作。
<div id ='app'>
<!-- 给子组件设置 ref 属性 -->
<com-a ref="comA"></com-a>
<!-- 给 HTML 标签设置 ref 属性 -->
<input type="text" ref="inp">
</div>
<script>
var ComA = {
template: `<div>组件A的内容:{{ value }}</div>`,
data () {
return {
value: '示例内容'
} } }
var vm = new Vue({
el: '#app',
methods: {
fn () {
// 点击后修改 HTML 标签焦点状态
this.$refs.inp.focus();
this.$refs.comA.value = '新的内容';
}
},
components: {
ComA
},
mounted () {
console.log(this.$refs);
this.$refs.comA.value = "修改后的内容";
},
});
</script>
组件插槽
组件插槽对组件进行内容设置
单个插槽
需要通过slot方式进行添加插槽,添加之后在标签之间书写内容就会被替换成新的内容。标签之前只能书写父组件的数据。由于标签结构属于父组件的视图,内部代码视图由父组件的模板渲染
<div id="app">
<com-a>
这里是父组件的视图模板,只能使用父组件的数据:
{{ parValue }}
</com-a>
<com-a></com-a> //插槽中没有内容时,采用默认内容
</div>
<script>
Vue.component('ComA', {
template: `
<div>
<h3>组件标题</h3>
<slot>
这是插槽的默认内容 //设置父组件中的内容,如果父组件中有内容就替换为父组件中的内容
</slot>
</div>
`,
data () {
return {
value: '子组件的数据'
} }
});
new Vue({
el: '#app',
data: {
parValue: '这是父组件的数据'
}
})
</script>
具名插槽
如果组件中多个位置需要设置插槽,需要给设置name属性。 v-slot属性只能书写在template标签中(只存在默认插槽同时又接受数据除外),不书写sollt中的name默认为default。v-slot可以简写成#插槽名
<div id ='app'>
<com-a>
<template v-slot:header>
<h3>组件的头部内容</h3>
</template>
<template #footer>
<p>组件底部内容</p>
</template>
</com-a>
</div>
<script>
// 子组件
Vue.component('ComA', {
template: `
<div>
<header>
<slot name="header"></slot>
</header>
<footer>
<slot name="footer"></slot>
</footer>
</div> `
});
new Vue({
el: '#app',
data: { }
});
</script>
作用域插槽
组件插槽内部只能用父组件的数据,利用作用域插槽可使用子组件的数据。使用v-bind绑定插槽;使用v-slot接受数据,Objval接受到的是包含所有插槽数据的对象。如果只存在默认插槽,同时有需要接受数据,可以不用写template标签并且将v-slot直接写在组件标签上。<com-a v-slot="dataObj">{{dataObj.value}}</com-a>
<div id='app'>
<com-a>
<tempalte v-slot:default="Objval">
{{ Objval.val }}
{{ Objval.num }}
</template>
</com-a>
</div>
<script>
var comA={
template:`
<div>
<solt :value="value"
:num="num"
></solt>
</div>
`,
data(){
return {
num:100,
value:'A组件数据'
}
}
}
};
new Vue({
el:"app",
components:{
components:{
ComA
}
}
})
</script>
可以使用ES6新语法将接收到的v-slot对象解构进行数据操作<template v-solt:default="{value,num}">
内置组件
动态组件
适用于实现多个组件频繁切换的处理。用于将一个‘元组件’渲染为动态组件,以is属性值决定渲染哪个组件。更改当前组件之后组件信息会被销毁之后创建新的组件。
<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
}
});
keep-alive组件
使用动态组件之后切换组件时组件会被频繁的渲染和销毁,keep-alive可用于保留组件状态避免组件重新渲染。用keep-alive标签将要保留的组件进行包裹即可。
<keep-alive>
<component :is="currentCom"></component>
</keep-alive>
include属性指定哪些组件会被缓存,include属性的值可以设置多个组件或者数组或者正则表达式
<keep-alive include="ComA,ComB,ComC"> //组件之间不可加空格
或<keep-alive include="['ComA','ComB','ComC']">
或者<keep-alive include="/Com[ABC]/">
<component :is="currentCom"></component>
</keep-alive>
exclude属性指定哪些组件不会被缓存。
<keep-alive exclude="ComD">
<component :is="currentCom"></component>
</keep-alive>
max属性用于设置最大缓存个数(去除最近最久未使用)
<keep-alive max='2'>
<component :is="currentCom"></component>
</keep-alive>
过渡组件
用于在Vue中插入、更新或者一处DOM时,提供多种不同方式的应用过渡、动画效果。
transition组件
用于给元素和组件添加进入/离开过渡。组件提供了6个class,用于设置过渡的具体效果
- 条件渲染(使用v-if)
进入/离开的类名
- v-enter/v-leave 入场/离开前的状态
- v-enter-to/v-leave-to 入场/离开完成的动画
- v-enter-active/v-leave-active 设置动画入场/离开过程的控制
<style>
.v-leave-to{ /*出场最终状态*/
opacity:0;
}
.v-leave-active{/*入场过程*/
transition: opacity 1s;
}
</style>
<transition>
<p v-if="showDemo">hello world</p>
</transition>
transition组件属性
name属性可用于多个元素、组件设置不同的过渡效果,这是需要将v-更改为对应的 name-的形式
<style>
.demo-leave-to{ /*出场最终状态*/
opacity:0;
}
.demo-leave-active{/*入场过程*/
transition: opacity 1s;
}
</style>
<transition name='demo'>
<p v-if="showDemo">hello world</p>
</transition>
- appear属性,可以让组件在初始渲染时实现过渡。从最后的设置的位置向初始位置移动
<transition appear name='demo'>/*appear默认为true*/
<p v-if="showDemo">hello world</p>
</transition>
自定义过渡类名
自定义过渡类名比普通类名优先级更高,在使用第三方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>
.test {
transition: all 3s;
}
</style>
<transition
enter-active-class="test"
leave-active-class="test"
>
<p v-if="show">这是 p 标签</p>
</transition>
使用第三方库
使用之前必须使用动画元素做基础类名animate__
<!-- 通过自定义过渡类名设置,给组件添加第三方动画库的类名效果 -->
<transition
enter-active-class="animate__bounceInDown"
leave-active-class="animate__bounceOutDown"
>
<!-- 必须给要使用动画的元素设置基础类名 animate__animated -->
<p
v-if="show"
class="animate__animated"
>hello world</p>
</transition>
transition-group组件
用于给列表统一设置过渡动画
- tag属性用于设置容器元素,默认为,可以修改为其他标签
- 过渡会应用与内部元素,而不是容器本身
- 子节点必须又独立的key,动画才能正常工作
/*不设置tag属性时,transition-group组件为span标签*/
<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; }
Storybook
可视化的组件展示平台,在隔离的开发环境中交互式的方式展示组件,独立开发组件