什么是nextTick()
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
这是一个生命周期钩子
this.$nextTick(回调函数)在下一次DOM更新结束后执行其指定的回调
原理
1.异步执行
Vue实现响应式并不是数据发生变化之后 DOM 就立即变化,而是按照一定策略进行 DOM 的更新。其实在Vue的文档中,不难发现Vue是异步执行 DOM 更新。
异步执行的机制简单来说就是:
- 1、首先所有的同步任何肯定是在主线程上执行的,即执行栈;
- 2、除了主线程,还一个任务队列task queue,只要异步任务有了运行结果,那任务队列中就会放置与之对应的一个事件。
- 3、等执行栈中,所有的同步任务执行完毕,系统就会读取任务队列中的事件,那么,事件对应的异步任务结束等待,进行执行栈,开始执行。
- 4、主线程不断重复上面的第三步。
Vue 在修改数据之后,视图不会立即更新,而是等待同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
2.事件循环说明
Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
上面说异步执行的机制中,主线程的执行过程就是一个tick
,而所有的异步结果都是通过任务队列来调度。Event Loop
(事件循环)分为宏任务和微任务,无论是执行宏任务还是微任务,完成后都会进入到一下tick
,并在两个tick
之间进行UI渲染。
由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据变化,并缓存在同一事件循环中,等同一数据循环中的所有数据变化完成之后,再统一进行视图更新。为了确保得到更新后的DOM,所以设置了 Vue.nextTick()
方法。
此时通过 Vue.nextTick 获取到改变后的 DOM 。同样,通过 setTimeout(fn, 0) 也可以获取到。
应用场景
1、主要场景:
需要在视图更新之后,基于新的视图进行操作。
注意:在 created 和 mounted 阶段,如果需要操作渲染后的视图,也要使用 nextTick 方法。
2、其他场景应用
① 点击按钮显示原本以 v-show = false 隐藏起来的输入框,并获取焦点。
② 点击获取元素宽度。
③ 使用 swiper 插件通过 ajax 请求图片后的滑动问题。
什么时候用:
当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
nextTick不能保证回调函数一定会执行,因为如果在执行前发生了错误或异常,那么回调函数就会被跳过。因此,在使用nextTick时要注意错误处理和异常捕获。
nextTick()用法:
- 全局中:
Vue.nextTick
- 组件中:
this.$nextTick
实例:
使用 $nextTick 优化 Todo-List
src/components/MyItem.vue
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
<span v-show="!todo.isEdit">{{ todo.title }}</span>
<input type="text" v-show="todo.isEdit" :value="todo.title"
@blur="handleBlur(todo, $event)" ref="inputTitle"/>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
<button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">
编辑
</button>
</li>
</template>
<script>
export default {
name: "MyItem",
props: ["todo"], // 声明接收todo
methods: {
handleCheck(id) { // 勾选or取消勾选
// 通知App组件将对应的todo对象的done值取反
// this.checkTodo(id)
this.$bus.$emit("checkTodo", id);
},
handleDelete(id) { // 删除
if (confirm("确定删除吗?")) {
// 通知App组件将对应的todo对象删除
// this.deleteTodo(id)
this.$bus.$emit('deleteTodo',id)
}
},
handleEdit(todo) { // 编辑
if (todo.hasOwnProperty("isEdit")) {
todo.isEdit = true;
} else {
this.$set(todo, "isEdit", true);
}
this.$nextTick(function () {
this.$refs.inputTitle.focus();
});
},
handleBlur(todo, e) { // 失去焦点回调(真正执行修改逻辑)
todo.isEdit = false;
if (!e.target.value.trim()) return alert("输入不能为空!");
this.$bus.$emit("updateTodo", todo.id, e.target.value);
},
},
};
</script>