Vue3 nextTick 使用教程

Vue3 nextTick 使用教程

1.nextTick 的基本概念


nextTick 是 Vue 提供的一个全局 API,用于在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,可以获取更新后的 DOM。

2.为什么需要 nextTick


Vue 的响应式系统会将一个事件循环内的所有数据变更缓冲到一个队列中,并异步地一次性更新DOM。这种机制可以避免不必要的计算和DOM操作。但有时我们需要基于更新后的DOM状态来执行某些操作,这时就需要使用 nextTick。

3.具体使用场景

3.1 在数据变化后访问更新后的DOM

<template>
  <div>
    <div ref="messageDiv">{{ message }}</div>
    <button @click="updateMessage">更新消息</button>
  </div>
</template>

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

const message = ref('Hello')
const messageDiv = ref(null)

const updateMessage = async () => {
  message.value = 'Updated Message'
  
  // 错误方式:此时DOM还未更新
  console.log(messageDiv.value.textContent) // 仍然显示 "Hello"
  
  // 正确方式:等待DOM更新后再访问
  await nextTick()
  console.log(messageDiv.value.textContent) // 显示 "Updated Message"
}
</script>

3.2 处理需要基于最新DOM尺寸的操作

<template>
  <div>
    <div ref="expandingDiv" :style="{ height: expanded ? 'auto' : '100px' }">
      <!-- 内容 -->
    </div>
    <button @click="expandAndScroll">展开并滚动</button>
  </div>
</template>

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

const expanded = ref(false)
const expandingDiv = ref(null)

const expandAndScroll = async () => {
  expanded.value = true
  
  // 等待DOM更新完成后再获取新的高度
  await nextTick()
  const newHeight = expandingDiv.value.offsetHeight
  window.scrollTo({
    top: newHeight,
    behavior: 'smooth'
  })
}
</script>

3.3 在动态组件渲染后执行操作

<template>
  <div>
    <component :is="currentComponent" ref="dynamicComponent" />
    <button @click="switchComponent">切换组件</button>
  </div>
</template>

<script setup>
import { ref, nextTick } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'

const currentComponent = ref(ComponentA)
const dynamicComponent = ref(null)

const switchComponent = async () => {
  currentComponent.value = ComponentB
  
  // 等待新组件渲染完成后再执行操作
  await nextTick()
  dynamicComponent.value.someMethod()
}
</script>

3.4 处理表单输入后的自动调整

<template>
  <div>
    <textarea
      ref="textarea"
      v-model="content"
      @input="adjustHeight"
    ></textarea>
  </div>
</template>

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

const content = ref('')
const textarea = ref(null)

const adjustHeight = async () => {
  // 等待文本内容更新到DOM
  await nextTick()
  
  // 调整textarea高度
  const textArea = textarea.value
  textArea.style.height = 'auto'
  textArea.style.height = textArea.scrollHeight + 'px'
}
</script>

4. nextTick 的不同使用方式

4.1 Promise 方式(推荐)

const updateDOM = async () => {
  try {
    await nextTick()
    // DOM操作
  } catch (error) {
    console.error('DOM更新失败:', error)
  }
}

4.2 回调函数方式

const updateDOM = async () => {
  try {
    await nextTick()
    // DOM操作
  } catch (error) {
    console.error('DOM更新失败:', error)
  }
}

5.注意事项和最佳实践

注意事项和最佳实践
1.避免过度使用:只在真正需要等待DOM更新后才执行的操作中使用 nextTick。

2.优先使用计算属性和侦听器:很多情况下,使用计算属性或侦听器可以避免直接操作DOM,从而避免使用 nextTick。

3.异常处理:在使用 async/await 语法时,建议使用 try/catch 处理可能的错误:

const updateDOM = async () => {
  try {
    await nextTick()
    // DOM操作
  } catch (error) {
    console.error('DOM更新失败:', error)
  }
}

4.性能考虑:多个 nextTick 操作会在同一个tick中批量执行,但仍应避免在一个操作中多次调用 nextTick。

5.组件生命周期:在 mounted 钩子中,如果需要访问子组件的DOM,也需要使用 nextTick:

onMounted(async () => {
  await nextTick()
  // 现在可以安全地访问所有子组件的DOM
})

6.elementplus示例

①此处使用nextTick可以让其中代码按顺序执行,若多个nextTick并行使用也可以达到此效果

// 打开弹窗
const openDialog = async(row: any) => {
    nextTick(()=>{
        airOrderAirInCargoFormRef.value?.resetFields();//表单数据清空重置
    });
    nextTick(()=>{
        //若订单主键存在
        if(row)
        {
            //编辑此页面
            getListOrderAirInCargoDetail(row);//查询空运货物信息并赋值给页面
        }
        //在进入此页面时才执行此方法
        console.log("现在执行空运货物信息openDialog方法");
        console.log("空运货物信息openDialog-ROW参数:",row);
    });
};

②nextTick中若使用了await异步执行接口,则nextTick后面需要跟着async关键词。

    //根据业务类型,加载订单类别字典
    nextTick(() => {
        loaddata.orderTypeName = [];
    });
    nextTick(async () => {
        let ordertypeDictData = await getDictDataList({DictTypeCode:'order_type',DictDataLabel:ruleForm.value.busType}); 
        loaddata.orderTypeName= ordertypeDictData.data.result; 
    });