首先会通过module.hot.accept监听文件变化,并传入该文件的渲染函数:
module.hot.accept(/*! ./App.vue?vue&type=template&id=472cff63&scoped=true& */ "./App.vue?vue&type=template&id=472cff63&scoped=true&", __WEBPACK_OUTDATED_DEPENDENCIES__ => { /* harmony import */ _App_vue_vue_type_template_id_472cff63_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./App.vue?vue&type=template&id=472cff63&scoped=true& */ "./App.vue?vue&type=template&id=472cff63&scoped=true&"); | |
(function () { | |
api.rerender('472cff63', { | |
render: _App_vue_vue_type_template_id_472cff63_scoped_true___WEBPACK_IMPORTED_MODULE_0__.render, | |
staticRenderFns: _App_vue_vue_type_template_id_472cff63_scoped_true___WEBPACK_IMPORTED_MODULE_0__.staticRenderFns | |
}) | |
})(__WEBPACK_OUTDATED_DEPENDENCIES__); }) |
随后执行rerender方法,只贴重点部分代码:
record.Ctor.options.render = options.render | |
record.Ctor.options.staticRenderFns = options.staticRenderFns | |
record.instances.slice().forEach(function (instance) { | |
instance.$options.render = options.render | |
instance.$options.staticRenderFns = options.staticRenderFns | |
// reset static trees | |
// pre 2.5, all static trees are cached together on the instance | |
if (instance._staticTrees) { | |
instance._staticTrees = [] | |
} | |
// 2.5.0 | |
if (Array.isArray(record.Ctor.options.cached)) { | |
record.Ctor.options.cached = [] | |
} | |
// 2.5.3 | |
if (Array.isArray(instance.$options.cached)) { | |
instance.$options.cached = [] | |
} | |
// post 2.5.4: v-once trees are cached on instance._staticTrees. | |
// Pure static trees are cached on the staticRenderFns array | |
// (both already reset above) | |
// 2.6: temporarily mark rendered scoped slots as unstable so that | |
// child components can be forced to update | |
var restore = patchScopedSlots(instance) | |
instance.$forceUpdate() | |
instance.$nextTick(restore) | |
}) |
首先会给当前的VueComponent的Options和实例里面的渲染函数替换为最新,然后执行实例上的forceUpdate方法:
Vue.prototype.$forceUpdate = function () { | |
var vm = this; | |
if (vm._watcher) { | |
vm._watcher.update(); | |
} | |
}; |
该方法是执行watcher实例的update方法:
Watcher.prototype.update = function () { | |
/* istanbul ignore else */ | |
if (this.lazy) { | |
this.dirty = true; | |
} | |
else if (this.sync) { | |
this.run(); | |
} | |
else { | |
queueWatcher(this); | |
} | |
}; | |
function queueWatcher(watcher) { | |
var id = watcher.id; | |
if (has[id] != null) { | |
return; | |
} | |
if (watcher === Dep.target && watcher.noRecurse) { | |
return; | |
} | |
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$1 && 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); | |
} | |
} |
重点看 queueWatcher(this),将最新的watcher放入quene队列并且将flushSchedulerQueue函数传给nextTick。
function nextTick(cb, ctx) { | |
var _resolve; | |
callbacks.push(function () { | |
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 | |
if (!cb && typeof Promise !== 'undefined') { | |
return new Promise(function (resolve) { | |
_resolve = resolve; | |
}); | |
} | |
} |
在callbacks里面放入一个执行回调的函数。并执行timerFunc():
timerFunc = function () { | |
p_1.then(flushCallbacks); | |
// In problematic UIWebViews, Promise.then doesn't completely break, but | |
// it can get stuck in a weird state where callbacks are pushed into the | |
// microtask queue but the queue isn't being flushed, until the browser | |
// needs to do some other work, e.g. handle a timer. Therefore we can | |
// "force" the microtask queue to be flushed by adding an empty timer. | |
if (isIOS) | |
setTimeout(noop); | |
}; |
该方法异步执行flushCallbacks:
function flushCallbacks() { | |
pending = false; | |
var copies = callbacks.slice(0); | |
callbacks.length = 0; | |
for (var i = 0; i < copies.length; i++) { | |
copies[i](); | |
} | |
} |
开始执行回调函数我们第一次放进去的函数flushSchedulerQueue:
function flushSchedulerQueue() { | |
currentFlushTimestamp = getNow(); | |
flushing = true; | |
var watcher, id; | |
// Sort queue before flush. | |
// This ensures that: | |
// 1. Components are updated from parent to child. (because parent is always | |
// created before the child) | |
// 2. A component's user watchers are run before its render watcher (because | |
// user watchers are created before the render watcher) | |
// 3. If a component is destroyed during a parent component's watcher run, | |
// its watchers can be skipped. | |
queue.sort(sortCompareFn); | |
// do not cache length because more watchers might be pushed | |
// as we run existing watchers | |
for (index$1 = 0; index$1 < queue.length; index$1++) { | |
watcher = queue[index$1]; | |
if (watcher.before) { | |
watcher.before(); | |
} | |
id = watcher.id; | |
has[id] = null; | |
watcher.run(); | |
// in dev build, check and stop circular updates. | |
if (has[id] != null) { | |
circular[id] = (circular[id] || 0) + 1; | |
if (circular[id] > MAX_UPDATE_COUNT) { | |
warn$2('You may have an infinite update loop ' + | |
(watcher.user | |
? "in watcher with expression \"".concat(watcher.expression, "\"") | |
: "in a component render function."), watcher.vm); | |
break; | |
} | |
} | |
} | |
// keep copies of post queues before resetting state | |
var activatedQueue = activatedChildren.slice(); | |
var updatedQueue = queue.slice(); | |
resetSchedulerState(); | |
// call component updated and activated hooks | |
callActivatedHooks(activatedQueue); | |
callUpdatedHooks(updatedQueue); | |
// devtool hook | |
/* istanbul ignore if */ | |
if (devtools && config.devtools) { | |
devtools.emit('flush'); | |
} | |
} |
首先触发watcher.before(),该方法是beforeUpdate的hook。然后执行watcher.run()方法:
Watcher.prototype.run = function () { | |
if (this.active) { | |
var value = this.get(); | |
if (value !== this.value || | |
// Deep watchers and watchers on Object/Arrays should fire even | |
// when the value is the same, because the value may | |
// have mutated. | |
isObject(value) || | |
this.deep) { | |
// set new value | |
var oldValue = this.value; | |
this.value = value; | |
if (this.user) { | |
var info = "callback for watcher \"".concat(this.expression, "\""); | |
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info); | |
} | |
else { | |
this.cb.call(this.vm, value, oldValue); | |
} | |
} | |
} | |
}; |
该方法watcher.get方法,该方法会重新执行render方法生成vnode,然后调用update方法更新节点:
updateComponent = function () { | |
vm._update(vm._render(), hydrating); | |
}; |
总结:
1.获取监听文件最新的render和staticRenderFns并赋值给当前的VueComponent和当前vm实例。
2.使用$forceUpdate添加当前的vm的watcher并在queue中,最后异步执行flushSchedulerQueue,该函数遍历quene执行watcher的run方法,该方法会执行vm实例的_update方法完成更新。