Skip to content

生命周期组合式API

setup

js
立即调用。可以替换之前的beforeCreate以及created,Vue3没有beforeCreate以及created

onMounted()

注册一个回调函数,在组件挂载完成后执行。

js
组件在以下情况下被视为已挂载
	其所有同步子组件都已经被挂载 (不包含异步组件或 <Suspense> 树内的组件)
	其自身的 DOM 树已经创建完成并插入了父容器中。注意仅当根容器在文档中时,才可以保证组件 DOM 树也在文档中。
vue
<script setup>
import { ref, onMounted } from 'vue'

const el = ref()

onMounted(() => {
  el.value // <div>
})
</script>

<template>
  <div ref="el"></div>
</template>

onBeforeMount()

注册一个钩子,在组件被挂载之前被调用。

js
function onBeforeMount(callback: () => void): void

当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。

onUpdated()

注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用

访问更新后的 DOM

vue
<script setup>
import { ref, onUpdated } from 'vue'

const count = ref(0)

onUpdated(() => {
  // 文本内容应该与当前的 `count.value` 一致
  console.log(document.getElementById('count').textContent)
})
</script>

<template>
  <button id="count" @click="count++">{{ count }}</button>
</template>

onUnmounted()

注册一个回调函数,在组件实例被卸载之后调用。

OnbeforeUpdate --> onBeforeUnmount --> onUnmounted --> onUpdated

js
一个组件在以下情况下被视为已卸载
	其所有子组件都已经被卸载
    所有相关的响应式作用 (渲染作用以及 setup() 时创建的计算属性和侦听器) 都已经停止
    可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接
    这个钩子在服务器端渲染期间不会被调用
    组件被销毁的过程:
vue
<script setup>
import { onMounted, onUnmounted } from 'vue'

let intervalId
onMounted(() => {
  intervalId = setInterval(() => {
    // ...
  })
})

onUnmounted(() => clearInterval(intervalId))
</script>

onBeforeUpdate()

注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。

js
function onBeforeUpdate(callback: () => void): void

onBeforeUnmount()

注册一个钩子,在组件实例被卸载之前调用

OnbeforeUpdate --> onBeforeUnmount --> onUpdated

js
function onBeforeUnmount(callback: () => void): void
js
当这个钩子被调用时,组件实例依然还保有全部的功能。

使用案例

  1. 入口文件:src->App.vue
vue
<template>
    <h3>生命周期组合API--->{{count}}</h3>
    <button @click="count++">更改count</button>
    <hr/>
    <button @click="isRender=!isRender">销毁子组件</button>
    <Child v-if="isRender"></Child>
</template>
<script lang="ts" setup>
import {onBeforeMount,onUpdated, onBeforeUpdate, onMounted, ref} from "vue";
    
// 直接将组件引用即可使用,不需要使用components进行注册
import Child from "./components/Child.vue";
	const count = ref(100);
	const isRender = ref(true);
	console.log("立即调用。可以替换之前的beforeCreate以及created");

// 当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。
// 它即将首次执行 DOM 渲染过程
onBeforeMount(function () {
     console.group("********onBeforeMount:注册一个钩子,在组件被挂载之前被调用。**************")
     console.log("onBeforeMount->count",count.value);// onBeforeMount->count 100
     console.log("onBeforeMount->h3",document.querySelector("h3"));// onBeforeMount->h3 null
     console.groupEnd();
});

// 注册一个回调函数,在组件挂载完成后执行
onMounted(function () {
     console.group("********onMounted:注册一个回调函数,在组件挂载完成后执行。。**************")
     console.log("onMounted->count",count.value);// onMounted->count 100
    // as 是typescript中的断言语法,指定变量的类型
     console.log("onMounted->h3",(document.querySelector("h3") as HTMLHeadingElement).innerText);// onMounted->h3 生命周期组合API--->100
     console.groupEnd();
});

// 注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用
onBeforeUpdate(function(){
     console.group("********onBeforeUpdate:注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。**************")
     console.log("onBeforeUpdate->count",count.value);// onBeforeUpdate->count 101
     console.log("onBeforeUpdate->h3",(document.querySelector("h3") as HTMLHeadingElement).innerText);// onBeforeUpdate->h3 生命周期组合API--->100
     console.groupEnd();
})

// 注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用
onUpdated(function(){
     console.group("********onUpdated:注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。。**************")
     console.log("onUpdated->count",count.value);// onUpdated->count 101
     console.log("onUpdated->h3",(document.querySelector("h3") as HTMLHeadingElement).innerText);// onUpdated->h3 生命周期组合API--->101
     console.groupEnd();
})
</script>
  1. 子组件
vue
<template>
    <div>
        <h4>Child</h4>
    </div>
</template>

<script setup lang="ts">
import {onBeforeUnmount, onBeforeUpdate, onUnmounted} from "vue";
let userName = "zhangpeiyue";
    
// 注册一个钩子,在组件实例被卸载之前调用 当这个钩子被调用时,组件实例依然还保有全部的功能。
onBeforeUnmount(function(){
    console.group("********onBeforeUnmount:注册一个钩子,在组件实例被卸载之前调用。。**************")
    console.log("onBeforeUnmount->userName",userName);// zhangpeiyue
    console.log("onBeforeUnmount->h3",document.querySelector("h4"));// <h4>Child</h4>
    console.groupEnd();
})
// 注册一个回调函数,在组件实例被卸载之后调用 
onUnmounted(function(){
    console.group("********onUnmounted:注册一个回调函数,在组件实例被卸载之后调用。。**************")
    console.log("onUnmounted->userName",userName);// zhangpeiyue
    console.log("onUnmounted->h3",document.querySelector("h4"));// null
    console.groupEnd();
})
</script>

<style scoped>

</style>

生命周期异步函数

ts
// 生命周期钩子中不写 异步函数,异步函数单独写出来
onMounted(()=>{
    getTradeMarkList()
})
// 定义异步函数
const getTradeMarkList = async () => {
    await 调用接口
}

computed()

接受一个 getter 函数,返回一个只读的响应式 ref 对象。

该 ref 通过 .value 暴露 getter 函数的返回值。

它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。

类型

js
// 只读
function computed<T>(
  getter: (oldValue: T | undefined) => T,
  // 查看下方的 "计算属性调试" 链接
  debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>

// 可写的
function computed<T>(
  options: {
    get: (oldValue: T | undefined) => T
    set: (value: T) => void
  },
  debuggerOptions?: DebuggerOptions
): Ref<T>

创建一个只读的计算属性ref

js
const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 错误

创建一个可写的计算属性 ref

js
const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

调试

js
const plusOne = computed(() => count.value + 1, {
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})

使用案例

  1. 引入组合API->computed

  2. computed接收一个回调函数

  3. 将computed函数的返回结果赋值给常量,变量

  4. 模板中可以直接渲染计算属性。

  5. 计算属性拥有缓存功能,只有所依赖的数据不发生改变,计算属性对应的函数不会再次执行

  6. 模板中如果使用了计算属性,那么在初次渲染时,会执行

  1. 入口文件:src->App.vue
vue
<template>
    <h3>计算属性-只读</h3>
    <p @click="count++">count--->{{count}}</p>
    <p>userName--->{{userName}}</p>
    <p>age--->{{age}}</p>

    <p>方案一:函数-->{{getInfo()}}</p>
    <p>方案二:拼接-->{{"今年"+userName+","+age+"岁了"}}</p>
    <p>方案三:计算属性-->{{getInfoCom}}</p>
    

    <button @click="age++">点我更改age</button>
    <button @click="userName+='!'">点我更改userName</button>

    <hr/>
    <h3>计算属性-读写</h3>
    <p @click="count+=2">count-->{{count}}</p>
    <p>countCom->{{countCom}}</p>
    <button @click="countCom=300">修改计算属性的值</button>
</template>
<script setup lang="ts">
    // 引入API
    import {ref,computed} from "vue";
    
    const count = ref(100);
    const age = ref(1);
    const userName = ref("zhangsan");

    // getInfo函数的返回结果,只受userName,以及age的影响,
    // 但是由于更新了数据count,视图需要更新,由于模板中调用了该函数,所以该函数依然会被执行,哪怕结果没有任何变化。于是需要计算属性,只监测所需要的数据变化时,再执行
    // 创建一个普通函数 getInfo函数
    const getInfo = function(){
        console.log("getInfo");
        return "今年"+userName.value+","+age.value+"岁了"
    }
    // 计算属性:
    // 1- 引入组合API->computed
    // 2- computed接收一个回调函数
    // 3- 将computed函数的返回结果赋值给常量,变量
    // 4- 模板中可以直接渲染计算属性。
    // 5- 计算属性拥有缓存功能,只有所依赖的数据不发生改变,计算属性对应的函数不会再次执行。
    // 6- 模板中如果使用了计算属性,那么在初次渲染时,会执行。
    
    // 创建一个只读的计算属性(参数是一个函数)
    const getInfoCom = computed(function(){
        console.log("getInfoCom")
        return "今年"+userName.value+","+age.value+"岁了"
    })
    
    // 创建一个读写的计算属性(参数是一个对象)
    const countCom = computed({
        // 返回值即是计算属性的值
        get(){
            return count.value*2;
        },
        // 当设置计算属性值时调用。接收的参数是修改的值
        set(v:number){
           count.value = v/2;
        }
    })
</script>

watch()

侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数

watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数

详细信息

js
第一个参数是侦听器的源。这个来源可以是以下几种
	一个函数,返回一个值
    一个 ref
    一个响应式对象
    或是由以上类型的值组成的数组
    
第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。
当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。

第三个可选的参数是一个对象,支持以下这些选项:
	immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined
	deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器。
	flush:调整回调函数的刷新时机。参考回调的刷新时机及 watchEffect()。
	onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器。
	once:回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。
tsx
watchEffect() 相比,watch() 使我们可以:
	懒执行副作用;
	更加明确是应该由哪个状态触发侦听器重新执行;
	可以访问所侦听状态的前一个值和当前值。

类型

js
// 侦听单个来源
function watch<T>(
  source: WatchSource<T>,
  callback: WatchCallback<T>,
  options?: WatchOptions
): StopHandle

// 侦听多个来源
function watch<T>(
  sources: WatchSource<T>[],
  callback: WatchCallback<T[]>,
  options?: WatchOptions
): StopHandle

type WatchCallback<T> = (
  value: T,
  oldValue: T,
  onCleanup: (cleanupFn: () => void) => void
) => void

type WatchSource<T> =
  | Ref<T> // ref
  | (() => T) // getter
  | T extends object
  ? T
  : never // 响应式对象

interface WatchOptions extends WatchEffectOptions {
  immediate?: boolean // 默认:false
  deep?: boolean // 默认:false
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
  once?: boolean // 默认:false (3.4+)
}

侦听一个 getter 函数

js
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

侦听一个 ref

js
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})

当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:

js
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。如果你想让回调在深层级变更时也能触发,你需要使用 { deep: true } 强制侦听器进入深层级模式。在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。

js
const state = reactive({ count: 0 })
watch(
  () => state,
  (newValue, oldValue) => {
    // newValue === oldValue
  },
  { deep: true }
)

当直接侦听一个响应式对象时,侦听器会自动启用深层模式:

js
const state = reactive({ count: 0 })
watch(state, () => {
  /* 深层级变更状态所触发的回调 */
})

watch()watchEffect() 享有相同的刷新时机和调试选项:

js
watch(source, callback, {
  flush: 'post',
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})

停止侦听器

js
const stop = watch(source, callback)

// 当已不再需要该侦听器时:
stop()

副作用清理

id 变化时,cancel 将被调用,

js
watch(id, async (newId, oldId, onCleanup) => {
  const { response, cancel } = doAsyncWork(newId)
  // 当 `id` 变化时,`cancel` 将被调用,
  // 取消之前的未完成的请求
  onCleanup(cancel)
  data.value = await response
})

使用案例

watch:可以针听一个或多个数据,如果侦听的数据发生变化,会执行指定的函数。

js
第一个参数是侦听的数据。第二个参数是回调函数。第三个参数是一个配置对象
当侦听的数据发生改变,那么会执行回调函数。
watch的返回值是一个函数,通过调用该函数可以停止侦听(当数据发生改变,那么不会再次执行回调函数)。
js
// watch:可以针听一个或多个数据,如果侦听的数据发生变化,会执行指定的函数。
import {reactive, ref, watch} from "vue";
// count可以被称为:响应式ref对象|ref对象|ref
const count = ref(1);
// info可以被称为:响应式代理对象|响应式reactive对象|数据源代理对象
const info = reactive({
    userName: "zhangsan",
    age: 12,
    arr: [1, 2, 3, 4]
})
vue
<template>
    <h3>watch</h3>
    <p>ref->count->{{ count }}</p>
    <p>reactive->info->{{ info }}</p>
    <button @click="count++">更改ref->count</button>
    <br/>
    <button @click="info.age++">更改reactive->info->age</button>
    <button @click="info.userName+='!'">更改reactive->info->userName</button>
    <button @click="info.arr.push(info.arr.length+1)">更改reactive->info->arr</button>
    <button @click="info.arr[1]=90">更改reactive->info->arr->下标</button>
    <button @click="stopWatch">停止侦听</button>
</template>
<script setup lang="ts">
// watch:可以针听一个或多个数据,如果侦听的数据发生变化,会执行指定的函数。
import {reactive, ref, watch} from "vue";
// count可以被称为:响应式ref对象|ref对象|ref
const count = ref(1);
// info可以被称为:响应式代理对象|响应式reactive对象|数据源代理对象
const info = reactive({
    userName: "zhangsan",
    age: 12,
    arr: [1, 2, 3, 4]
})

// 1- 侦听ref
// 第一个参数是侦听的数据。第二个参数是回调函数。第三个参数是一个配置对象。
// 当侦听的数据发生改变,那么会执行回调函数。
// watch的返回值是一个函数,通过调用该函数可以停止侦听(当数据发生改变,那么不会再次执行回调函数)。

const stopWatch = watch(count,(newValue,oldValue)=> {
     console.log("watch", newValue, oldValue)
},{
     // 立即调用,如果立即调用那么oldValue的值是undefined
     immediate:true
})

// 2- 侦听数据代理对象
const stopWatch = watch(info,(newValue,oldValue)=>{
     console.log("侦听数据代理对象",newValue===oldValue) // true 引用地址没有改变
 },{
     immediate:true
});

// 3- 侦听一个函数,该函数返回一个数据
// 函数要有一个返回值,返回值可以依赖一个或多个数据,当返回值发生改变以后会被侦听到。
const stopWatch = watch(()=>count.value+info.age,(newValue,oldValue)=>{
     console.log("侦听一个函数",newValue,oldValue)
},{
     immediate:true
})

// 主要应用于侦听代理对象中的某个属性的变化
// 如果返回的是一个引用类型,引用类型的地址没有发生改变不会被侦听,如果被侦听需要增加deep:true
const stopWatch = watch(()=>info.age,(newValue,oldValue)=>{
    console.log("侦听一个函数",newValue,oldValue)
},{
    // deep:true,
    immediate:true
})

// 4- 侦听数组,数组内可以是所有的响应式数据
// newValue是更改后的数据,oldValue是更改前的数据。
// newValue以及oldValue类型是数组。数组中分别为对应数据的更改前以及更改后的值
// info的引用类型的地址没有发生改变,newvalue和oldValue值是一样的。
const stopWatch = watch([info,count],(newValue,oldValue)=>{
     // newValue:[info更改后的值,count更改后的值]
     // oldValue:[info更改前的值,count更改前的值]
     console.log("侦听数组",newValue,oldValue)
 },{
     immediate:true
})

</script>

watchEffect()

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

tsx
watchEffect是组合API
watchEffect是一个函数
watchEffect,接收一个回调函数,且该函数会被立即调用
// 特点
 1.	watchEffect()中的回调函数会立即调用
 2. 回调函数中所依赖的数据发生改变,会执行回调函数

类型

tsx
function watchEffect(
  effect: (onCleanup: OnCleanup) => void,
  options?: WatchEffectOptions
): StopHandle

type OnCleanup = (cleanupFn: () => void) => void

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

type StopHandle = () => void

详细信息

tsx
第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求 (参见下面的示例)。

第二个参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。

默认情况下,侦听器将在组件渲染之前执行。设置 flush: 'post' 将会使侦听器延迟到组件渲染之后再执行。详见回调的触发时机。在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。这可以通过设置 flush: 'sync' 来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。

返回值是一个用来停止该副作用的函数。

使用

js
const count = ref(0)

watchEffect(() => console.log(count.value))
// -> 输出 0

count.value++
// -> 输出 1

副作用清除

清除副作用函数执行的时机

  1. 当所依赖的数据发生改变会执行清除副作用函数。
  2. 当停止时执行。
  3. 当组件被销毁时执行。
js
watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(id.value)
  // `cancel` 会在 `id` 更改时调用
  // 以便取消之前
  // 未完成的请求
  onCleanup(cancel)
  data.value = await response
})

停止侦听器

js
const stop = watchEffect(() => {})

// 当不再需要此侦听器时:
stop()

选项

js
watchEffect(() => {}, {
  flush: 'post',
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})

使用案例

  1. 入口文件:src->App.vue
vue
<template>
    <h3>watchEffect</h3>
    <p @click="count++">count-->{{count}}</p>
    <p @click="obj.age++">obj.age-->{{obj.age}}</p>
    <hr/>
    <button @click="isRender=!isRender">显示与隐藏</button>
    <Child v-if="isRender"/>
</template>
<script setup lang="ts">
import Child from "./components/Child.vue";
    
// 1- watchEffect是组合API
// 2- watchEffect是一个函数
// 3- watchEffect,接收一个回调函数,且该函数会被立即调用
import {onMounted, reactive, ref, watchEffect} from "vue";
const isRender = ref(true);
// 一- 立即调用
watchEffect(function(){
     console.log("over");
})

// 二- 回调函数中所依赖的数据发生改变,会执行回调函数。
const count = ref(1);
const obj = reactive({
    age:12
})
// 当count发生改变那么会执行
watchEffect(()=>{
     console.log(count.value);
})

// 当count或obj.age发生改变,会执行回调函数。
watchEffect(()=>{
     console.log(count.value,obj.age);
})

// 三- 清除副作用函数 onInvalidate

// 不需要指定侦听的数据,回调函数的参数是一个函数onInvalidate,onInvalidate函数是为了清理副作用
    
// 清除副作用函数执行的时机    
// 1.当所依赖的数据发生改变会执行清除副作用函数。
// 2.当停止时执行。
// 3.当组件被销毁时执行。
const stop =  watchEffect(function(onInvalidate){
     console.log(count.value,obj.age);
     onInvalidate(function(){
         console.log("清理副作用!")
     })
})
// 四- 停止侦听器
setTimeout(()=>{
     stop();
},5000)



</script>
  1. 子组件:用于演示 watchEffect 函数的回调函数执行的时机:当停止时执行,当组件被销毁时执行
vue
<template>
<div>Child->{{num}}</div>
</template>

<script setup lang="ts">
import {onBeforeUnmount, onMounted, ref, watchEffect} from "vue";
// 定义一个响应式num
const num = ref(1);

// 方式1:通过onBeforeUnmount钩子函数来执行清除定时器(在组件被销毁前)
// 定义一个定时器标记
let timer = -1;
// 定义一个钩子函数,清除定时器
onBeforeUnmount(function(){
     clearInterval(timer);
 })
// 立即执行定时器
onMounted(function(){
     timer = setInterval(()=>{
         num.value++;
         console.log(11111)
     },1000)
})    
    

// 方式2:通过watchEffect函数的回调函数onInvalidate来执行清除定时器(在组件被销毁前)
// 新建一个侦听器
watchEffect(function(onInvalidate){
    console.log("watchEffect回调函数");
    // 新建一个多次定时器
    const timer = setInterval(function(){
        console.log("定时器");
        num.value++;
    },1000)
    onInvalidate(function(){
        console.log("onInvalidate");
        clearInterval(timer);
    })
})



</script>

<style scoped>

</style>

watch与watchEffect异同点

js
1- watchEffect与watch的初次执行时机不同。
2- watchEffect回调函数所依赖的数据发生改变会调用,watch是指定的侦听数据发生改变才会执行(有第一个参数指定侦听数据)
3- watchEffect无法获取修改前的值。watch可以。
4- watchEffect拥有清理副作用函数。

// 总结:watch与watchEffect有何不同?
// 1- watchEffect与watch的初次执行时机不同。
// 2- watchEffect回调函数所依赖的数据发生改变会调用,watch是指定的侦听数据发生改变才会执行(有第一个参数指定侦听数据)。
// 3- watchEffect无法获取修改前的值。watch可以。
// 4- watchEffect拥有清理副作用函数。

nextTick()

背景:数据发生改变,但是视图并不会立即发生改变

等待下一次 DOM 更新刷新的工具方法。

使用nextTick函数立即渲染更新DOM

tsx
setup是在beforecreate钩子函数前执行,而视图是在onUpdated,OnMounted之后才渲染完成,所以在setup阶段获取DOM元素,就需要nextTick()函数立即更新渲染DOM,才能避免undefined的问题。

类型

js
function nextTick(callback?: () => void): Promise<void>

详细信息

tsx
当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。
这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。仅执行一次这个onBeforeUpdate钩子函数。

nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。
你可以传递一个回调函数作为参数,或者 await 返回的 Promise

使用

vue
<script setup>
import { ref, nextTick } from 'vue'

const count = ref(0)

async function increment() {
  count.value++

  // DOM 还未更新
  console.log(document.getElementById('counter').textContent) // 0

  await nextTick()
  // DOM 此时已经更新
  console.log(document.getElementById('counter').textContent) // 1
}
</script>

<template>
  <button id="counter" @click="increment">{{ count }}</button>
</template>

使用案例

演示:数据发生改变,但是视图并不会立即发生改变

nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。

vue
<template>
    <div>nextTick:{{count}}</div>
    <div>nextTick:{{userName}}</div>
    <button @click="changeNum">count+1</button>
</template>
<script setup lang="ts">
import {onBeforeUpdate, onUpdated, ref,nextTick} from "vue";
// 定义一个基本数据类型
const count = ref(1);
const userName = ref("zhangsan");
    
// 定义一个方法
const changeNum = async function(){
    count.value++; // 当数据改变时,并不会立即更新视图,而是进入一个队列
    userName.value+="!"; // 当所有的异步都执行完之后
    
    // 数据发生改变,但是真实的DOM渲染在此时并没有发生改变
    // nextTick:1	nextTick:zhangsan
    console.log("changeNum",(document.querySelector("div") as  HTMLDivElement).innerText)
    
    // 情况1:使用nextTick函数立即更新DOM,参数是一个回调函数
    nextTick(function(){
        // 可以立即获取更新以后的视图信息
		console.log("nextTick",(document.querySelector("div") as  HTMLDivElement).innerText)
	})

    // 情况2:使用nextTick函数立即更新DOM,作为一个Promise对象
    nextTick().then(()=>{
    console.log("nextTick",(document.querySelector("div") as  HTMLDivElement).innerText)
    })
    
	// 情况3:使用nextTick函数立即更新DOM,作为一个异步函数
    await nextTick();
    console.log("nextTic",(document.querySelector("div") as  HTMLDivElement).innerText)
}

// 定义一个更新前钩子
onBeforeUpdate(function(){
     console.log("onBeforeUpdate") 
 })

// 定义一个更新后的钩子
 onUpdated(function(){
     // 可以获取更新以后的视图信息
     console.log("onUpdated",(document.querySelector("div") as  HTMLDivElement).innerText);
})
</script>

nextTick应用1

使用nextTick()函数,作为异步函数使用,立即渲染视图

vue
<template>
    <button @click="changeIsRender">点我</button>
    <div ref="divRef" v-if="isRender"></div>
</template>
<script setup lang="ts">
import {ref,nextTick} from "vue";
    
const isRender = ref(false);
const divRef = ref();
const changeIsRender = async function(){
    // isRender取反
    isRender.value = !isRender.value;
    // 判断是否渲染
    if(isRender.value){
        // 立即视图更新
        await nextTick();
        divRef.value.style.width="300px";
        divRef.value.style.height="300px";
        divRef.value.style.background = "red";
    }
}
</script>

nextTick应用2

ref和v-for结合使用,使用nextTick立即更新视图

vue
<template>
    <p ref="pRef" v-for="(item,index) in state.userList" :key="index">{{item}}</p>
</template>
<script setup lang="ts">
import {ref, nextTick, reactive} from "vue";
// 定义一个类型 TState
type TState = {
    // 定义userList是一个元素是string类型的数组
    userList:string[]
}
// 定义一个Proxy对象
const state = reactive<TState>({
    userList: []
});
// 定义一个Proxy对象    
const pRef = ref();
// 定义一个定时器
setTimeout(() => {
    
    const data = ["张三", "李四", "王五", "赵六"];
    // 把数组赋值给userList,数据发生了改变
    state.userList = data;
    // 视图立即渲染改变
    nextTick(function(){
        // ref和v-for结合使用得到一个数组,数组中的元素是DOM元素对象
        // 遍历这个数组
        pRef.value.forEach((item:HTMLParagraphElement,index:number)=>{
            // 设置间隔的元素对象的背景颜色
            if(index % 2){
                item.style.background = "red";
            }else{
                item.style.background = "green";
            }
        })
    })

}, 3000)
</script>

nextTick应用3

输入框的编辑和完成操作,并获取光标。

vue
<template>
    <div>
        <input ref="inputRef" v-if="isEdit" v-model="str" type="text">
        <span v-else>{{ str }}</span>

        <button @click="onEdit">{{isEdit?"完成":"编辑"}}</button>
    </div>
</template>
<script setup lang="ts">
import {ref, nextTick, reactive} from "vue";

let str = ref("你现在过的还好吗?");
let isEdit = ref(false);
let inputRef = ref();
// 定义一个函数
const onEdit = function(){
    // 取反
    isEdit.value = !isEdit.value;
    if(isEdit.value){
        // 立即执行回调函数
        nextTick(function(){
            // 获取光标
            inputRef.value.focus();
        })

    }
}
</script>