导航栏自定义按钮,通过dom获取到该按钮,修改文本,数据是修改了,但是视图没有改过来,使用了nexttick也不可以。
1.使用Vue提供的$forceUpdate()方法强制刷新视图
mounted() {
// 获取自定义按钮节点
const btn = document.querySelector('.custom-btn')
// 修改按钮文本
btn.innerText = '新文本'
// 强制刷新视图
this.$forceUpdate()
}
如果你不看源码 一定是百思不得其解的, 因为vue有时候确实设计的非常精妙
笔者来用自己的方法给你写一写你能够看的明白的$nextTick, 跟着注释看我相信你是不会迷路的
const nextTick = (function() {
let callbacks = []; // 最后所有在nextTick中传递过来的函数都会进入这个数组
let timerHandler = () => { // 这个函数用来延迟nexTick传递进来的函数的执行
// Promise.resolve这句话往这里一站, 你就知道这哥们后面的那行then代码要等待了,
const p = Promise.resolve();
p.then(releaseCallbacks); // 等同步任务执行完毕这哥们会执行
}
function releaseCallbacks() { // 作为p.then的回调 releaseCallbacks肯定也会在微队列中等待
for(let i = 0; i < callbacks.length; i++) {
callbacks[i](); // 所有存储进callbacks的函数挨个执行
}
callbacks.length = 0;
}
// 真正暴露给用户的回调函数
return function(cb) {
callbacks.push(cb);
timerHandler();
}
}())
调用我们自己的的nextTick方法,其他语句都不变 我们走一遍输出发现输出结果如下
确实发现所有交付给nextTick的函数都按照异步执行了, 但是并没有如我们所想象的那样, 相反连nextTick真正的作用都发挥不上了, 我们不再可以监听到msg被更改, 于是我们来看看被笔者进行注释过后的真正的$nextTick源码(当然, 前提是上面笔者的这份简化版源码你已经看懂了, 不然vue源码会更加头大)
export let isUsingMicroTask = false // 这是vue用来判断是否启用微任务的锁, 如果不懂没关系他不重要
const callbacks = [] // 同样, 最后所有在nextTick中传递过来的函数都会进入这个数组
let pending = false // 异步锁, 如果同步任务未执行完, 异步锁肯定是锁住的
function flushCallbacks () { // 最终执行callbacks的函数
pending = false // 重置异步锁
// 这里我们发现将callbacks复制了一份给copies, 最终循环操作的也是copies, 这是因为不想造成nextTick嵌套调用的冲突
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc // 相当于上面的timerHandler
// 判断当前环境支不支持原生的Promise构造函数
if (typeof Promise !== 'undefined' && isNative(Promise)) {
// 如果支持会走上面的timerHandler的流程
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// 判断是不是IE
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Technically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
// 真正暴露出去的nextTick方法
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line 这个是新加的, 如果没有传递cb参数则返回一个新的promoise出去
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
抛开一些兼容性写法和一些容错机制来说, vue的nextTick和我们写的nextTick没有什么差别, 但是为什么会产生截然不同的效果呢?
继续阅读源码笔者有发现, vue中还存在一个queueWatcher
方法, 如下
function queueWatcher (watcher) {
var id = watcher.id;
if (has[id] == null) {
has[id] = true;
if (!flushing) {
queue.push(watcher);
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
var i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--;
}
queue.splice(i + 1, 0, watcher);
}
// queue the flush
if (!waiting) {
waiting = true;
if (!config.async) {
flushSchedulerQueue();
return
}
nextTick(flushSchedulerQueue);
}
}
}
你不用将他看懂, 但是笔者可以告诉你这哥们的作用就是用来更改nextTick的执行顺序的
本身我们执行nextTick他的效果跟一般的异步任务没什么太大的区别, 无非就是nextTick会被置于微任务, 而queueWatcher
方法和他带来的一些骚操作则改变了nextTick的运行轨迹
如果在$nextTick前没有更改vue监控的属性值的情况发生, 那么nextTick中的代码按照正常异步微任务走掉
如果在$nextTick前有更改了vue所监控的属性值的情况, 则queueWatcher会调换nextTick的执行顺序, nextTick将会在下一次事件循环vue刷新页面后执行
有些东西你不看源码是真的想破头都想不出来他到底是什么原因, 这也是我们作为开发者一直要追逐的事情, 共勉
回答:
在Vue.js中,使用$nextTick函数可以在下次DOM更新周期中获取数据的最新状态。但是在该问题中,即使使用了$nextTick函数,修改按钮文字后视图仍然没有改变。这可能是由于Vue.js采用了异步更新队列来进行更新DOM,所以在同一轮事件循环中,如果有多个数据发生了变化,组件的watcher会受到多次通知,但虚拟DOM只会进行一次渲染,从而导致视图没有改变。
解决该问题的方法是,在修改按钮文字的代码后直接触发一次重新渲染。在Vue.js中,可以调用$forceUpdate()方法手动强制更新渲染视图。代码如下所示:
this.btnText = 'New Text'; // 修改按钮文字
this.$forceUpdate(); // 强制更新视图
该方法会触发整个组件的重新渲染流程,更新视图中的所有数据,也可以在$nextTick函数的回调函数中执行。完整代码如下:
// 修改按钮文字
this.btnText = 'New Text';
this.$nextTick(() => {
this.$forceUpdate(); // 强制更新视图
});
这样就能及时更新DOM,使视图与数据同步。