组件生命周期
周期钩子函数
nexttick
nexttick 可以让回调函数在 dom 更新操作完成后才执行,dom 更新完立即执行。
- tick 时钟指针转动发出一下滴答声,表示时间走动,进入下一轮的意思。
比如我们有下面的需求:
- 点击一个按钮,我们会修改在h2中显示的message;
- message被修改后撑大了 h2,我们要获取 h2 被撑大后的高度;
存在的问题:
如果我们修改 message 后,直接去获取修改后的高度,高度将为 0,获取不到修改后的高度。再次修改 message 后,那就能获取到第一次修改 message 时,h2 变化后的高度。
实现上面的案例我们有三种方式:
方式一:在点击按钮后立即获取到h2的高度(错误的做法)
方式二:在updated生命周期函数中获取h2的高度(但是其他数据更新,也会执行该操作)
方式三:使用nexttick函数;
<template>
<div>
<h2>{{counter}}</h2>
<button @click="increment">+1</button>
<h2 class="title" ref="titleRef">{{message}}</h2>
<button @click="addMessageContent">添加内容</button>
</div>
</template>
<script>
import { ref, onUpdated, nextTick } from "vue";
export default {
setup() {
const message = ref("")
const titleRef = ref(null)
const counter = ref(0)
const addMessageContent = () => {
message.value += "哈哈哈哈哈哈哈哈哈哈"
// console.log(titleRef.value.offsetHeight) // 直接获取失败
// 放入微任务队列底部,等更新DOM后再执行获取操作
nextTick(() => {
console.log(titleRef.value.offsetHeight)
})
}
const increment = () => {
for (let i = 0; i < 100; i++) {
counter.value++
}
}
onUpdated(() => {
})
return {
message,
counter,
increment,
titleRef,
addMessageContent
}
}
}
</script>
<style scoped>
.title {
width: 120px;
}
</style>
nexttick 的原理
vue 内部维持了好几个队列来执行操作,比如部分周期钩子函数,组件更新,watch 回调等等,并且这些队列中的任务都会被添加到微任务队列中执行。
比如我们更新了 dom 中的数据,然后 vue 监听到变化,开始响应自己的任务队列进行处理,任务最终被添加到微任务队列。此时我们去获取 dom 更新后的数据,是获取不到的。因为获取 dom 数据是个同步任务,立即执行。此时微任务队列中的 vue 任务还没执行,所以获取不到。
而 nexttick 可以将本是同步任务的操作放入微任务队列的底部,这样就能在 vue 响应操作完成后再去获取数据,就能获取到了。
为什么 vue 要这么设计,将任务全添加到微任务队列?
因为可以提高性能。
比如我 for 循环更新100次 dom 内容,vue 的响应操作会执行 100 次吗?不会的,因为更新 dom 数据的 100 次操作是同步任务,会在微任务之前执行,所以 vue 的响应操作会等 100 次更新完,直接拿结果,然后开始响应操作,只执行了一次。这样就提高了效率。
组件的 v-model
前面我们在 dom 元素 input 中可以使用 v-model 来完成双向绑定:
- 这个时候往往会非常方便,因为 v-model 默认帮助我们完成了两件事,这也是 v-model 的实现原理。
v-bind:value
的数据绑定和@input
的事件监听,在事件回调函数中完成了数据从模板到实例的过程;
其实我们也可以在组件中使用 v-model。组件上的 v-model 自动绑定了modelValue
属性和自动监听了update
事件。
<template>
<div>
<!-- dom 元素上使用 v-model -->
<!-- <input v-model="message">
<input :value="message" @input="message = $event.target.value"> -->
<!-- 组件上使用 v-model -->
<!-- <hy-input v-model="message"></hy-input> -->
<hy-input :modelValue="message" @update:model-value="message = $event"></hy-input>
</div>
</template>
input dom 元素上使用 v-model,实际是监听 input 事件,事件响应函数中通过$event.target.value
将输入的值传给绑定的属性 value,完成了视图到 data 的数据流动。
而组件标签上没有 target 属性,直接使用$event
接收子组件传来的数据。
子组件就像 input 输入框,父组件就像 data。输入框接收 data 中绑定 value 的值,同样的子组件也要接收父组件的值,所以父向子传值,完成了数据从 data 流向视图的过程。
父组件中监听了 update 事件,子组件中可以触发 update 事件,也就是子向父传值,完成了数据从视图流向 data 的过程。
这样就实现了组件之间的双向数据绑定,组件上的 v-model 依然是个语法糖,帮我们绑定了变量,监听了事件。
<template>
<div>
<home :modelValue="message" @update:model-value="message = $event"></home>
<h2> 父组件:{{ message }}</h2>
</div>
</template>
<script>
import home from './components/home.vue';
export default {
components: { home },
data() {
return {
message: 'hhh'
}
}
}
</script>
------home------
<template>
<div>
<h2>父组件传来的值:{{modelValue}}</h2>
<input type="text" :value="modelValue" @keyup.enter="inputenter">
</div>
</template>
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
methods: {
inputenter(event) {
// 将子组件的值传给父组件
this.$emit('update:modelValue', event.target.value)
}
},
}
</script>
<template>
<div>
<h2> 父组件:{{ message }}</h2>
<home v-model="message"></home>
</div>
</template>
<script>
import home from './components/home.vue';
export default {
components: { home },
data() {
return {
message: 'hhh'
}
}
}
</script>
父组件如上,直接 v-model 很简单。并且背后将 modelValue 属性传递给子组件了。
那子组件能不能直接 v-model 直接双向绑定 modelValue 呢?<input type="text" v-model="modelValue">
这是不行的,因为比如子组件中这个 input 输入框,v-model 自动监听了 input 事件,但是它自动监听 input 事件并响应的时候并没有触发父组件自动监听的 update:modelValue 事件。所以子组件的值无法传给父组件,它只是在改 modelValue 的值,这和父组件没有半毛钱关系;而且 props 中的值最好不要修改。
computed 实现
可以让子组件中的 v-model 绑定计算属性解决这个问题。
计算属性有读和写的操作,get、set。
v-model 中的 v-bind 肯定要读取计算属性,调用 get 方法,我们就可以在 get 方法中返回 modelValue,相当于实现了原生实现中的:value="modelValue"
,整体上子组件这个“input”,获取到了父组件这个“data”的数据。
而当输入框输入时,v-model 响应 input 事件,将输入值写入计算属性中,调用 set 方法,这个 set 方法就相当于我们手动响应的 input 事件函数,所以就可以在其中触发 update:modelValue 事件,并将数据传到父组件。
<template>
<div>
<h2>父组件传来的值:{{modelValue}}</h2>
<!-- 手动实现 v-model -->
<!-- <input type="text" :value="modelValue" @keyup.enter="inputenter"> -->
<!-- v-model 绑定计算属性 -->
<input type="text" v-model="hhh">
</div>
</template>
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
// methods: {
// inputenter(event) {
// this.$emit('update:modelValue', event.target.value)
// }
// },
computed: {
hhh: {
set(value) {
this.$emit('update:modelValue', value)
},
get() {
return this.modelValue
}
}
}
}
</script>
组件中绑定多个 v-model
一个组件中可以绑定多个 v-model,针对子组件不同区域进行双向数据绑定。
我们知道,默认情况下的 v-model 其实是绑定了 modelValue 属性和 @update:modelValue的事件;
如果我们希望绑定更多,可以给 v-model 传入一个参数,那么这个参数的名称就是我们绑定属性的名称;
v-model:名称=""
:相当于绑定了 名称 属性和监听了 update:名称 事件。
v-model:title 相当于做了两件事:绑定了title属性;监听了 @update:title 的事件<home v-model="message" v-model:ppt="info"> </home>
props: ['modelValue', 'ppt'],
emits: ['update:modelValue', 'update:ppt'],
混入 Mixin
目前我们是使用组件化的方式在开发整个Vue的应用程序,但是组件和组件之间有时候会存在相同的代码逻辑,我
们希望对相同的代码逻辑进行抽取。
注意:混入是公共代码的抽取;公共模板抽取目前还没有,除非封装成一个组件;vuex 是数据共享和公共代码抽取不一样。
在Vue2和Vue3中都支持的一种方式就是使用 Mixin
来完成:
- 我们抽取公共的代码逻辑到一个 Mixin 对象中,一个 Mixin 对象可以包含任何组件选项,比如 data,methods 等;
- 当组件导入使用 Mixin 对象时,所有 Mixin 对象的选项将被混合进入该组件本身的选项中,相当于代码的合并。
```htmlexport const demoMixin = {
data() {
return {
message: "Hello DemoMixin"
}
},
methods: {
foo() {
console.log("demo mixin foo");
}
},
created() {
console.log("执行了demo mixin created");
}
}
{{message}}
情况一:如果是 data 函数的返回值对象 - 返回值对象默认情况下会进行合并; - 如果 data 返回值对象的属性发生了冲突,那么**会保留组件自身的数据**; 情况二:如果是生命周期钩子函数 - 生命周期的钩子函数的内容会被合并到数组中,**都会被执行**; 情况三:值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。 - 比如都有 methods 选项,并且都定义了方法,那么它们都会生效; - 但是如果对象的 key 相同,那么会**取本组件对象的键值对**; ## 全局混入 Mixin 如果组件中的某些选项代码逻辑,是所有的组件都需要拥有的,那么这个时候我们可以使用全局的 mixin:
全局的 Mixin 可以使用应用 app 的`mixin()` 方法来完成注册;一旦注册,那么全局混入的选项将会影响每一个组件; ```javascript import { createApp } from 'vue' import App from './App.vue' // 全局混入 app.mixin({ data() { return {} }, methods: { }, created() { console.log("全局的created生命周期"); } }); createApp(App).mount('#app') ``` # extends extends 允许声明扩展另外一个组件,类似于Mixin
extends 是早期 vue 实现公共代码抽取的方式,但是发现不够灵活就设计出了 mixin 。但是随着 vue3 composition API 的出现,混入也很少用了,都被扫入了历史的垃圾堆。 ```html
——app.vue——
{{message}}
```