This is the 22nd day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021
Why did Vue design nextTick
In frameworks like Vue or React, the biggest performance drain comes from diff calculations, and changing data, or setState, which triggers updates, means a single diff calculation. Without any optimization, frequent data changes can easily cause pages to lag.
As a result, both frameworks are designed to update the merge. For a while, react interview questions about whether setState was synchronous or asynchronous were almost a must-ask, and VUE’s Nexttick-related interview questions were common.
This installment will start with nextTick and explore how to merge updates in Vue.
What did nextTick do
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
if(! cb &&typeof Promise! = ='undefined') {
return new Promise(resolve= > {
_resolve = resolve
})
}
}
Copy the code
NextTick takes a function argument, puts the function into the Callbacks, and then calls timerFunc(), which is an empty array and is really just a microtask.
const callbacks = [] ... let timerFunc if (typeof Promise ! == 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) } }Copy the code
TimerFunc calls flushCallbacks, which makes a shallow copy of the callbacks and executes the functions in turn
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
Copy the code
So nextTick is essentially putting the callback into the microtask queue in the current EventLoop.
So why do they do it this way?
A deferred callback is performed after the next DOM update loop ends. Use this method immediately after modifying the data to get the updated DOM.
We can assume that changes to the data and updates to the DOM are asynchronous, so to get the updated DOM, we also need to wait asynchronously. So how does this asynchrony work?
Let’s start by looking at what Vue does from beforeCreate to Created.
Vue init Execution sequence
flowchart LR new["new Vue()"] --> _init subgraph _init initLifecycle-->initEvents-->initRender-->callHook1["callHook(vm, 'beforeCreate')"]-->initInjections-->initState-->initProvide-->callHook2["callHook(vm, 'created')"] end subgraph initState obdata["observe(data, true)"] --> newOb["ob = new Observer(value)"] --> walk["this.walk(value)"] --> defineReactive end
The thing to focus on here is defineReactive in initState
defineReactive
This method does the familiar object.defineProperty operation:
export function defineReactive (
obj: Object, key: string,val: any,customSetter?: ?Function,shallow? :boolean
) {...Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter () {... },set: function reactiveSetter (newVal) {...if (setter) {
setter.call(obj, newVal)
} else{ val = newVal } childOb = ! shallow && observe(newVal) dep.notify() } }) }Copy the code
Vue update execution sequence
After modifying the data, the set here is triggered, which makes the data change, and then calls dep.notify().
notify () { const subs = this.subs.slice() if (process.env.NODE_ENV ! == 'production' && ! config.async) { subs.sort((a, b) => a.id - b.id) } for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }Copy the code
These subs are Watcher after dependency collection, the Watcher will be added to the subs array
class Dep {
statictarget: ? Watcher; id:number;
subs: Array<Watcher>;
}
Copy the code
Watcher
Watcher has an important member VM that represents the component instance, and its update calls queueWatcher
class Watcher {
vm: Component; . update () { ... queueWatcher(this)}}Copy the code
queueWatcher
function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if(! flushing) { queue.push(watcher) }else {
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1.0, watcher)
}
// queue the flush
if(! waiting) { ... nextTick(flushSchedulerQueue) } } }Copy the code
At the end, nextTick is again encountered, and you can see the asynchronous update starting from there.
The gains and losses of asynchronous updates
To do performance tuning, you must do update merges. To do update merges, the first question is how do you determine that the update is over?
The idea of asynchronous update is that: data changes are completed in the macro task queue, when entering the micro task queue, these changes must have been completed, then only need to complete the update once.
The design is clear and the code is relatively simple, but the disadvantage is that the data is inconsistent with the DOM (after all, the DOM has to wait for the microtask to complete the update after the data is changed), so nextTick is necessary
So are there any synchronous updates that complete the update merge? There is, like React16
Click to modify the data and trigger the update:
Flowchart TD subgraph onClick S1 [setState data1] --> S2 [setState datA2] -->log["console.log(state)"] end; onClick --> Subgraph delegate s11[execute setState data1]--> s111[" get state copy, Start the update process "] --> s22[setState data1] --> s222[" S33 [" execute console.log(state)"] end subgraph enters the update life cycle merge multiple setState generated copies to get the complete state --> perform a one-time update end based on the new state
In synchronous mode, state and DOM remain the same. Dom is not updated and state remains the same.
However, there are a few disadvantages, such as counter-intuitive, setState is not named logically, instead of requestStateChange. In addition, state changes must occur where they can be monitored, such as life cycle, custom delegate events.
Of course, both of these designs can achieve the main goal of updating and merging.
How does Vue collect dependencies?
Of course, going back to the above, there is one unexplained question: when the subs array was put into watcher. Future articles will continue parsing the source code from this issue.