refactor: ensure watcher callbacks are deferred

This commit is contained in:
Evan You 2018-09-20 18:57:13 -04:00
parent 9b50a5abb9
commit bf38fea313
5 changed files with 38 additions and 17 deletions

View File

@ -44,7 +44,9 @@ export interface MountedComponent<D = Data, P = Data> extends Component {
destroyed?(): void
_updateHandle: Autorun
_queueJob: ((fn: () => void) => void)
$forceUpdate: () => void
$nextTick: (fn: () => void) => Promise<any>
_self: MountedComponent<D, P> // on proxies only
}
@ -68,6 +70,7 @@ export class Component {
public $options: any
public $proxy: any = null
public $forceUpdate: (() => void) | null = null
public $nextTick: ((fn: () => void) => Promise<any>) | null = null
public _rawData: Data | null = null
public _computedGetters: Record<string, ComputedGetter> | null = null
@ -76,6 +79,7 @@ export class Component {
public _destroyed: boolean = false
public _events: { [event: string]: Function[] | null } | null = null
public _updateHandle: Autorun | null = null
public _queueJob: ((fn: () => void) => void) | null = null
public _revokeProxy: () => void
public _isVue: boolean = true

View File

@ -1,6 +1,6 @@
import { MountedComponent } from './component'
import { ComponentWatchOptions } from './componentOptions'
import { autorun, stop, Autorun } from '@vue/observer'
import { autorun, stop } from '@vue/observer'
export function initializeWatch(
instance: MountedComponent,
@ -14,6 +14,7 @@ export function initializeWatch(
}
// TODO deep watch
// TODO sync watch
export function setupWatcher(
instance: MountedComponent,
keyOrFn: string | Function,
@ -21,22 +22,32 @@ export function setupWatcher(
): () => void {
const handles = instance._watchHandles || (instance._watchHandles = new Set())
const proxy = instance.$proxy
const rawGetter =
typeof keyOrFn === 'string'
? () => proxy[keyOrFn]
: () => keyOrFn.call(proxy)
let oldValue: any
const applyCb = () => {
const newValue = runner()
if (newValue !== oldValue) {
cb(newValue, oldValue)
oldValue = newValue
}
}
const runner = autorun(rawGetter, {
scheduler: (runner: Autorun) => {
const newValue = runner()
if (newValue !== oldValue) {
cb(newValue, oldValue)
oldValue = newValue
}
scheduler: () => {
// defer watch callback using the scheduler injected defer.
instance._queueJob(applyCb)
}
})
oldValue = runner()
handles.add(runner)
return () => {
stop(runner)
handles.delete(runner)

View File

@ -56,7 +56,10 @@ interface PatchDataFunction {
}
interface RendererOptions {
queueJob: (fn: () => void, postFlushJob?: () => void) => void
scheduler: {
nextTick: (fn: () => void) => Promise<any>
queueJob: (fn: () => void, postFlushJob?: () => void) => void
}
nodeOps: NodeOps
patchData: PatchDataFunction
teardownVNode?: (vnode: VNode) => void
@ -68,7 +71,7 @@ interface RendererOptions {
// renderer alongside an actual renderer.
export function createRenderer(options: RendererOptions) {
const {
queueJob,
scheduler: { queueJob, nextTick },
nodeOps: {
createElement: platformCreateElement,
createText: platformCreateText,
@ -1186,6 +1189,10 @@ export function createRenderer(options: RendererOptions) {
(__COMPAT__ && (parentVNode.children as MountedComponent)) ||
createComponentInstance(parentVNode, Component, parentComponent)
// renderer-injected scheduler methods
instance.$nextTick = nextTick
instance._queueJob = queueJob
const queueUpdate = (instance.$forceUpdate = () => {
queueJob(instance._updateHandle, flushHooks)
})

View File

@ -1,12 +1,15 @@
import { createRenderer, VNode } from '@vue/core'
import { queueJob } from '@vue/scheduler'
import { queueJob, nextTick } from '@vue/scheduler'
import { nodeOps } from './nodeOps'
import { patchData } from './patchData'
import { teardownVNode } from './teardownVNode'
const { render: _render } = createRenderer({
queueJob,
scheduler: {
queueJob,
nextTick
},
nodeOps,
patchData,
teardownVNode

View File

@ -17,18 +17,14 @@ export function queueJob(job: () => void, postFlushCb?: () => void) {
}
}
if (postFlushCb) {
queuePostFlushCb(postFlushCb)
postFlushCbs.push(postFlushCb)
}
if (!flushing) {
nextTick(flushJobs)
}
}
export function queuePostFlushCb(cb: () => void) {
postFlushCbs.push(cb)
}
export function flushJobs() {
function flushJobs() {
flushing = true
let job
while ((job = queue.shift())) {