From bf38fea31388dd8c3a2f4206221089a2ed92e841 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 20 Sep 2018 18:57:13 -0400 Subject: [PATCH] refactor: ensure watcher callbacks are deferred --- packages/core/src/component.ts | 4 ++++ packages/core/src/componentWatch.ts | 25 ++++++++++++++++++------- packages/core/src/createRenderer.ts | 11 +++++++++-- packages/renderer-dom/src/index.ts | 7 +++++-- packages/scheduler/src/index.ts | 8 ++------ 5 files changed, 38 insertions(+), 17 deletions(-) diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index 54cb0874..865c992e 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -44,7 +44,9 @@ export interface MountedComponent extends Component { destroyed?(): void _updateHandle: Autorun + _queueJob: ((fn: () => void) => void) $forceUpdate: () => void + $nextTick: (fn: () => void) => Promise _self: MountedComponent // 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) | null = null public _rawData: Data | null = null public _computedGetters: Record | 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 diff --git a/packages/core/src/componentWatch.ts b/packages/core/src/componentWatch.ts index a8387021..c18ff9ad 100644 --- a/packages/core/src/componentWatch.ts +++ b/packages/core/src/componentWatch.ts @@ -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) diff --git a/packages/core/src/createRenderer.ts b/packages/core/src/createRenderer.ts index 5ad36401..ca4962ca 100644 --- a/packages/core/src/createRenderer.ts +++ b/packages/core/src/createRenderer.ts @@ -56,7 +56,10 @@ interface PatchDataFunction { } interface RendererOptions { - queueJob: (fn: () => void, postFlushJob?: () => void) => void + scheduler: { + nextTick: (fn: () => void) => Promise + 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) }) diff --git a/packages/renderer-dom/src/index.ts b/packages/renderer-dom/src/index.ts index 423f9a1e..e1057c3f 100644 --- a/packages/renderer-dom/src/index.ts +++ b/packages/renderer-dom/src/index.ts @@ -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 diff --git a/packages/scheduler/src/index.ts b/packages/scheduler/src/index.ts index 8a30cb3a..e99840be 100644 --- a/packages/scheduler/src/index.ts +++ b/packages/scheduler/src/index.ts @@ -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())) {