目录
一、vue生命周期
1、组件生命周期
<div class="app"></div>
<script>
const app = new Vue({
el: '.app',
beforeCreate() {
console.log('组件即将创建前!');
},
created() {
console.log('组件创建完成!');
},
beforeMount() {
console.log('组件即将挂载前!');
},
mounted() {
console.log('组件挂载完成!');
},
beforeUpdate() {
console.log('组件即将更新前!');
},
updated() {
console.log('组件更新完成!')
},
activated() {
console.log('被缓存的组件激活时调用!');
},
deactivated() {
console.log('被缓存的组件停用时调用!');
},
beforeUnmount() {
console.log('组件即将被卸载前调用!');
},
unmounted() {
console.log('组件被卸载后调用!');
},
errorCaptured: (err, vm, info) => {
console.log('捕获到来自子组件的异常时调用!');
},
renderTracked({ key, target, type }) {
console.log('虚拟DOM重新渲染时调用!');
},
renderTriggered({ key, target, type }) {
console.log('虚拟DOM被触发渲染时调用!');
},
})
</script>
结果:
从控制台打印的信息可以看到,本次页面渲染过程中执行了四个组件的生命周期方法,这是由于我们使用的是vue根组件,页面的渲染过程中只执行了组件的创建和挂载过程,并没有执行卸载过程。
<div class="app">
<div class="text" v-if="show">hello world</div>
<button @click="showHandle">点我</button>
</div>
<script>
const app = new Vue({
el: '.app',
data() {
return {
show: false
}
},
methods: {
showHandle() {
this.show = !this.show
}
},
beforeCreate() {
console.log('组件即将创建前!');
},
created() {
console.log('组件创建完成!');
},
beforeMount() {
console.log('组件即将挂载前!');
},
mounted() {
console.log('组件挂载完成!');
},
beforeUpdate() {
console.log('组件即将更新前!');
},
updated() {
console.log('组件更新完成!')
},
activated() {
console.log('被缓存的组件激活时调用!');
},
deactivated() {
console.log('被缓存的组件停用时调用!');
},
beforeUnmount() {
console.log('组件即将被卸载前调用!');
},
unmounted() {
console.log('组件被卸载后调用!');
},
errorCaptured: (err, vm, info) => {
console.log('捕获到来自子组件的异常时调用!');
},
renderTracked({ key, target, type }) {
console.log('虚拟DOM重新渲染时调用!');
},
renderTriggered({ key, target, type }) {
console.log('虚拟DOM被触发渲染时调用!');
},
})
</script>
结果:
点击按钮后:
<div class="app">
<button @click="count++">点我{{count}}</button>
</div>
<script>
const app = new Vue({
el: '.app',
data() {
return {
count: 0
}
},
beforeCreate() {
console.log('组件即将创建前!');
},
created() {
console.log('组件创建完成!');
},
beforeMount() {
console.log('组件即将挂载前!');
},
mounted() {
console.log('组件挂载完成!');
},
beforeUpdate() {
console.log('组件即将更新前!');
},
updated() {
console.log('组件更新完成!')
},
activated() {
console.log('被缓存的组件激活时调用!');
},
deactivated() {
console.log('被缓存的组件停用时调用!');
},
beforeUnmount() {
console.log('组件即将被卸载前调用!');
},
unmounted() {
console.log('组件被卸载后调用!');
},
errorCaptured: (err, vm, info) => {
console.log('捕获到来自子组件的异常时调用!');
},
renderTracked({ key, target, type }) {
console.log('虚拟DOM重新渲染时调用!');
},
renderTriggered({ key, target, type }) {
console.log('虚拟DOM被触发渲染时调用!');
},
})
</script>
结果:
点击按钮后:
从两次测试来看,我们可以看出常用的生命周期方法有哪些
renderTracked()虚拟DOM重新渲染时调用、renderTriggered()虚拟DOM被触发渲染时调用、beforeUpdate()组件即将更新前、updated() 组件更新完成。
2、应用全局的配置选项
当调用Vue.createApp方法后,会创建一个Vue应用实例,对于此应用实例,其内部封装了一个config对象,我们可以通过这个对象的一些全局选项来对其配置。常用的配置项有异常与警告捕获配置和全局属性配置。
<div class="app">
</div>
<script>
const { createApp } = Vue
const app = createApp({
data() {
return {
count: 0
}
}
})
app.mount('.app')
app.config.errorHandler = function (err, vm, info) { };
app.config.warnHandler = function (msg, vm, trace) { };
</script>
全局数据可以通过globalProperties全局属性对象进行配置。
<div id="app">
<test></test>
</div>
<script>
const testComponent = {
template: `<h2>hello world</h2>`,
mounted() {
console.log(this.name);
},
}
const app = Vue.createApp({
mounted() {
console.log(this.name);
},
})
app.config.globalProperties = {
name: 'zs'
}
app.component('test', testComponent)
app.mount('#app')
</script>
结果:
3、组件的注册方式
组件的注册方式分为全局注册和局部注册两种。直接使用应用实例的component方法注册的组件是全局组件,即可以在应用内的任何地方使用这些组件。 在实例内部通过components注册的组件时局部组件。
如果创建组件时使用驼峰命名,调用组件的时候需要将驼峰改为横线-写法
<div class="app">
<h2>hello world</h2>
<global-com></global-com>
</div>
<script>
const app = Vue.createApp({})
//注册为局部组件
const localComponent = {
template: `<div>注册为局部组件</div>`
}
//注册为全局组件
const globalComponent = {
components: {
'localCom': localComponent
},
template: `<div>
注册为全局组件
<local-com></local-com>
</div>`,
}
app.component('globalCom', globalComponent)
app.mount('.app')
</script>
结果:
二、组件props属性进行验证
1、对prop属性进行验证
某个自定义组件需要使用使用props进行外部传值,如果其要接受的参数为一个数值,但是最终调用方法传递了一个字符串类型的数据,则组件内部难免会出现错误,vue在定义组件的props时,可以通过添加约束的方式来对其类型、默认值、是否选填等配置。
<div class="app">
<com :count="10"></com>
</div>
<script>
const com = {
template: `<div>
<button @click="num++">点击</button>
<div class='text'>{{total}}</div>
</div>`,
props: ['count'],
data() {
return {
num: 0
}
},
computed: {
total() {
return this.num + this.count
}
}
}
const app = Vue.createApp({
components: {
'com': com
}
})
app.mount('.app')
</script>
结果:
注意,在外部传递数值类型的数据带组件内部时,必需使用v-bind指令的方式进行传递,直接使用html属性设置的方式会将传递的数据作为字符串传递。
<com count="10"></com>
结果:
我们可以对定义的props进行约束来显示地指定其类型。当将组件的props配置项配置为列表时,其表示当前定义的属性没有任何约束控制,如果将其配置为对象,则可以进行更多约束设置。
<div class="app">
<com :count="10"></com>
</div>
<script>
const com = {
template: `<div>
<button @click="num++">点击</button>
<div class='text'>{{total}}</div>
</div>`,
props: {
count: {
type: Number,
required: false,
default: 10
}
},
data() {
return {
num: 0
}
},
computed: {
total() {
return this.num + this.count
}
}
}
const app = Vue.createApp({
components: {
'com': com
}
})
app.mount('.app')
</script>
如果传入的值类型不正确,控制台会输出警告信息。
<com count="10"></com>
结果:
如果只需要指定属性的类型,而不需要指定更加复杂的性质。
count1: String,
count2: Number,
count3: Boolean,
count4: Array,
count5: Object,
count6: Function,
如果一个属性可能是多种类型
count7: [String, Number, Boolean]
在对属性的默认值进行配置时,如果默认值的获取方式比较复杂,也可以将其定义为函数,函数执行的结果会被当作当前属性的默认值。
count: {
default: function () {
return 20
}
}
2、props的只读性质
我们不能再组件内部修改props属性的值。
<div class="app">
<com :count="10"></com>
</div>
<script>
const com = {
template: `<div>
<button @click="count++">点击</button>
<div class='text'>{{count}}</div>
</div>`,
props: {
count: Number
},
}
const app = Vue.createApp({
components: {
'com': com
}
})
app.mount('.app')
结果:
3、组件数据注入
数据注入是一种便捷的组件间数据传递方式。一般情况下,当父组件需要传递数据到子组件时,我们会使用props,但是当组件的嵌套层级很多,子组件需要使用多层之外的父组件的数据时,就非常麻烦,数据需要一层一层地进行传递。
数据注入,是指父组件可以向其所有子组件提供数据,不论在层级结构上次子组件的层级有多深。实现数据注入需要使用组件的provide和inject两个配置项,父组件设置provide配置项来提供数据,子组件需要设置inject配置项来获取数据。
<div class="app">
<list></list>
</div>
<script>
const labels = {
template: `<div class="label" v-for="item in count" style="width: 50px;height: 40px;border: 1px solid #efefef;float: left;margin-left: 10px;margin-top:5px"></div>`,
inject: ['count']
}
const item = {
props: ['count'],
template: `<div class="item" v-for="item in count" style="width: 800px;height: 50px;background-color: #ccc;margin: 35px auto;"><labels></labels></div>`,
components: {
"labels": labels
}
}
const list = {
template: `<div class="list" style="width: 1000px;height: 500px;border: 1px solid #ccc;"><item :count=count></item></div>`,
components: {
"item": item
},
data() {
return {
count: 5
}
},
provide() {
return {
count: this.count
}
}
}
const app = Vue.createApp({
components: {
"list": list
}
})
app.mount('.app')
</script>
结果:
三、组件Mixin技术
1、使用Mixin来定义组件
当我们开发大型前端项目时,可能会定义非常多的组件,这些组件中可能有部分功能是通用的,对于这部分通用的功能,如果每个组件都编写一遍会非常繁琐,而且不易于维护。
这时,我们可以定义一个混入对象,混入对象中可以包含任意的组件定义选项,当此对象被混入组件时,组件会将混入对象中提供的选项引入当前组件内部。
<div class="app">
<com1 title="zs"></com1>
<com1 title="ls"></com1>
<com1 title="ww"></com1>
</div>
<script>
const com1 = {
props: ['title'],
template: `<h3>hello {{title}}</h3>`,
}
const com2 = {
props: ['title'],
template: `<h3>hello {{title}}</h3>`,
}
const com3 = {
props: ['title'],
template: `<h3>hello {{title}}</h3>`,
}
const app = Vue.createApp({
components: {
"com1": com1
}
})
app.mount('.app')
使用Mixin技术简化代码
<div class="app">
<com1 title="zs"></com1>
<com1 title="ls"></com1>
<com1 title="ww"></com1>
</div>
<script>
const myMixin = {
props: ['title'],
template: `<h3>hello {{title}}</h3>`,
}
const com1 = {
mixins: [myMixin]
}
const com2 = {
mixins: [myMixin]
}
const com3 = {
mixins: [myMixin]
}
const app = Vue.createApp({
components: {
"com1": com1,
"com2": com2,
"com3": com3,
}
})
app.mount('.app')
</script>
结果:
2、Mixin选项的合并
当混入对象与组件中定义了相同的选项时,vue可以非常智能地对这些选项进行合并。不冲突的配置将完整合并,冲突的配置会以组件中自己的配置为准。生命周期函数不会进行合并,会先触发Mixin的生命周期函数,再触发组件内部的生命周期函数。
<div class="app">
<com></com>
</div>
<script>
const myMixin = {
data() {
return {
a: "a",
b: "b",
c: "c",
d: "d"
}
},
mounted() {
console.log('Mixin的mounted()');
},
}
const com = {
mixins: [myMixin],
data() {
return {
a: "a",
b: "B",
e: "e"
}
},
mounted() {
console.log(`a:${this.a}-b:${this.b}-c:${this.c}-d:${this.d}-e:${this.e}`);
console.log('组件的mounted()');
},
template: `<div></div>`
}
const app = Vue.createApp({
components: {
"com": com
}
})
app.mount('.app')
</script>
结果:
3、全局Mixin
app.mixin({})
全局Mixin会为所有注册的组件默认混入,当程序出现问题,这会增加排查问题的难度,但是全局Mixin非常适合开发插件。
四、使用自定义指令
1、认识自定义指令
有时候我们仍需要直接操作DOM元素来实现业务功能,这时候就可以使用自定义指令。
调用实例的directive方法可以注册全局的自定义指令,在使用自定义指令时需要加上v-前缀。
input输入框自定获取焦点:
<div class="app">
<input type="text" v-get-focus>
</div>
<script>
const app = Vue.createApp({})
app.directive("getFocus", {
mounted(element) {
element.focus()
},
})
app.mount('.app')
</script>
注册局部的自定义指令
<div class="app">
<com></com>
</div>
<script>
const com = {
template: `<h2>局部定义</h2><input type="text" v-get-focus>`,
directives: {
getFocus: {
mounted(element) {
element.focus()
},
}
}
}
const app = Vue.createApp({
components: {
"com": com
}
})
app.mount('.app')
</script>
结果:
2、自定义指令的参数
自定义指令也可以设置值和参数,这些设置数据会通过一个param对象传递到指令中实现的生命周期方法中。
<div class="app">
<input type="text" v-get-focus="1">
</div>
<script>
const app = Vue.createApp({})
app.directive("getFocus", {
mounted(element, param) {
element.focus()
console.log('我是getFocus参数:' + param.value);
},
})
app.mount('.app')
</script>
结果:
五、使用组件的Teleport功能开发全局弹窗
Teleport可以将相关行为的逻辑和ui封装到同一个组件中,提高代码的聚合性。
<div class="app">
<com></com>
</div>
<script>
const com = {
template: `<button class="pop" @click="show=true">弹出弹窗</button>
<div v-if="show" class="popupwindow" style="width: 300px;
height: 200px;
border: 1px solid #000;
position: absolute;
top: 45%;
left: 35%;">
<div class="text" style="height: 50px;
width: 100%;
text-align: center;">弹窗</div>
<button @click="show=false" class="hide" style="display: block;
margin: 0 auto;">隐藏弹窗</button>
</div>`,
data() {
return {
show: true
}
}
}
const app = Vue.createApp({
components: {
"com": com
}
})
app.mount('.app')
</script>
结果:
如果在其他组件内使用的话。
<div class="app">
<com1></com1>
</div>
<script>
const com = {
template: `<button class="pop" @click="show=true">弹出弹窗</button>
<div v-if="show" class="popupwindow" style="width: 300px;
height: 200px;
border: 1px solid #000;
position: absolute;
top: 45%;
left: 35%;">
<div class="text" style="height: 50px;
width: 100%;
text-align: center;">弹窗</div>
<button @click="show=false" class="hide" style="display: block;
margin: 0 auto;">隐藏弹窗</button>
</div>`,
data() {
return {
show: true
}
}
}
const com1 = {
template: `<div style="width:200px;height:100px;border:1px solid #efefef;position:absolute"><com></com></div>`,
components: {
"com": com,
}
}
const app = Vue.createApp({
components: {
"com1": com1
}
})
app.mount('.app')
</script>
结果:
使用teleport
<div class="app">
<com1></com1>
</div>
<script>
const com = {
template: `<button class="pop" @click="show=true">弹出弹窗</button>
<teleport to="body">
<div v-if="show" class="popupwindow" style="width: 300px;
height: 200px;
border: 1px solid #000;
position: absolute;
top: 45%;
left: 35%;">
<div class="text" style="height: 50px;
width: 100%;
text-align: center;">弹窗</div>
<button @click="show=false" class="hide" style="display: block;
margin: 0 auto;">隐藏弹窗</button>
</div>
</teleport>`,
data() {
return {
show: true
}
}
}
const com1 = {
template: `<div style="width:200px;height:100px;border:1px solid #efefef;position:absolute"><com></com></div>`,
components: {
"com": com,
}
}
const app = Vue.createApp({
components: {
"com1": com1
}
})
app.mount('.app')
</script>
结果: