]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: renderWatch / renderEffect (#86)
authorRizumu Ayaka <rizumu@ayaka.moe>
Sat, 6 Jan 2024 17:31:01 +0000 (01:31 +0800)
committerGitHub <noreply@github.com>
Sat, 6 Jan 2024 17:31:01 +0000 (01:31 +0800)
* refactor: use baseWatch to drive watchAPI

* feat: basic implementation of renderWatch and effectId

* chore: remove effect id

* chore: export and simplify types

* test: render watch

* chore: add todo comment

* fix: sync code changes according to the review in PR #82

* fix: enum values conflict

* chore: rename

* feat: change watchEffect to renderEffect in compiler-vapor

* chore: update

---------

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
15 files changed:
packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vHtml.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vText.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
packages/compiler-vapor/__tests__/transforms/vOn.spec.ts
packages/compiler-vapor/src/generate.ts
packages/runtime-vapor/__tests__/apiWatch.spec.ts
packages/runtime-vapor/__tests__/renderWatch.spec.ts [new file with mode: 0644]
packages/runtime-vapor/src/apiWatch.ts
packages/runtime-vapor/src/errorHandling.ts
packages/runtime-vapor/src/index.ts
packages/runtime-vapor/src/renderWatch.ts [new file with mode: 0644]
packages/runtime-vapor/src/scheduler.ts

index 3693cd197e3c89800775a0af3219316dfed9938d..e658e4a2fb68f346dfd4c2366199339b4eb61eb0 100644 (file)
@@ -1,7 +1,7 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
 exports[`compile > bindings 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, insert as _insert, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, insert as _insert, renderEffect as _renderEffect, setText as _setText } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div>count is <!>.</div>")
@@ -9,7 +9,7 @@ export function render(_ctx) {
   const { 0: [n3, { 1: [n2],}],} = _children(n0)
   const n1 = _createTextNode(_ctx.count)
   _insert(n1, n3, n2)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n1, undefined, _ctx.count)
   })
   return n0
@@ -121,7 +121,7 @@ export function render(_ctx) {
 `;
 
 exports[`compile > directives > v-pre > self-closing v-pre 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, watchEffect as _watchEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, renderEffect as _renderEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div><div><Comp></Comp></div>")
@@ -129,10 +129,10 @@ export function render(_ctx) {
   const { 1: [n2],} = _children(n0)
   const n1 = _createTextNode(_ctx.bar)
   _append(n2, n1)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n1, undefined, _ctx.bar)
   })
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setAttr(n2, "id", undefined, _ctx.foo)
   })
   return n0
@@ -140,7 +140,7 @@ export function render(_ctx) {
 `;
 
 exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, watchEffect as _watchEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, renderEffect as _renderEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div :id=\\"foo\\"><Comp></Comp>{{ bar }}</div><div><Comp></Comp></div>")
@@ -148,10 +148,10 @@ export function render(_ctx) {
   const { 1: [n2],} = _children(n0)
   const n1 = _createTextNode(_ctx.bar)
   _append(n2, n1)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n1, undefined, _ctx.bar)
   })
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setAttr(n2, "id", undefined, _ctx.foo)
   })
   return n0
@@ -159,7 +159,7 @@ export function render(_ctx) {
 `;
 
 exports[`compile > dynamic root 1`] = `
-"import { fragment as _fragment, createTextNode as _createTextNode, append as _append, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
+"import { fragment as _fragment, createTextNode as _createTextNode, append as _append, renderEffect as _renderEffect, setText as _setText } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _fragment()
@@ -168,10 +168,10 @@ export function render(_ctx) {
   const n1 = _createTextNode(1)
   const n2 = _createTextNode(2)
   _append(n0, n1, n2)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n1, undefined, 1)
   })
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n2, undefined, 2)
   })
   return n0
@@ -179,7 +179,7 @@ export function render(_ctx) {
 `;
 
 exports[`compile > dynamic root nodes and interpolation 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, on as _on, watchEffect as _watchEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, on as _on, renderEffect as _renderEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<button>foo<!>foo</button>")
@@ -192,7 +192,7 @@ export function render(_ctx) {
   _insert(n2, n4, n5)
   _append(n4, n3)
   _on(n4, "click", (...args) => (_ctx.handleClick && _ctx.handleClick(...args)))
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n1, undefined, _ctx.count)
     _setText(n2, undefined, _ctx.count)
     _setText(n3, undefined, _ctx.count)
@@ -207,7 +207,7 @@ exports[`compile > expression parsing > interpolation 1`] = `
   const t0 = _fragment()
 
   const n0 = t0()
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n0, undefined, a + b.value)
   })
   return n0
@@ -219,7 +219,7 @@ exports[`compile > expression parsing > v-bind 1`] = `
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setAttr(n1, key.value+1, undefined, _unref(foo)[key.value+1]())
   })
   return n0
@@ -237,7 +237,7 @@ export function render(_ctx) {
 `;
 
 exports[`compile > static + dynamic root 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, renderEffect as _renderEffect, setText as _setText } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("3<!>6<!>9")
@@ -255,28 +255,28 @@ export function render(_ctx) {
   _insert([n3, n4], n0, n9)
   _insert([n5, n6], n0, n10)
   _append(n0, n7, n8)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n1, undefined, 1)
   })
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n2, undefined, 2)
   })
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n3, undefined, 4)
   })
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n4, undefined, 5)
   })
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n5, undefined, 7)
   })
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n6, undefined, 8)
   })
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n7, undefined, 'A')
   })
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n8, undefined, 'B')
   })
   return n0
index 63936f19e956ff0e2c99f09140931769b547c2d7..fa70b0f374c85e7e39c4e8ea64c72fee5336fdfd 100644 (file)
@@ -1,13 +1,13 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
 exports[`compiler v-bind > .camel modifier 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setAttr(n1, "fooBar", undefined, _ctx.id)
   })
   return n0
@@ -21,7 +21,7 @@ export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setAttr(n1, _camelize(_ctx.foo), undefined, _ctx.id)
   })
   return n0
@@ -29,13 +29,13 @@ export function render(_ctx) {
 `;
 
 exports[`compiler v-bind > .camel modifier w/ no expression 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setAttr(n1, "fooBar", undefined, _ctx.fooBar)
   })
   return n0
@@ -43,13 +43,13 @@ export function render(_ctx) {
 `;
 
 exports[`compiler v-bind > basic 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setAttr(n1, "id", undefined, _ctx.id)
   })
   return n0
@@ -57,13 +57,13 @@ export function render(_ctx) {
 `;
 
 exports[`compiler v-bind > dynamic arg 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setAttr(n1, _ctx.id, undefined, _ctx.id)
   })
   return n0
@@ -71,13 +71,13 @@ export function render(_ctx) {
 `;
 
 exports[`compiler v-bind > no expression (shorthand) 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setAttr(n1, "camel-case", undefined, _ctx.camelCase)
   })
   return n0
@@ -85,13 +85,13 @@ export function render(_ctx) {
 `;
 
 exports[`compiler v-bind > no expression 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setAttr(n1, "id", undefined, _ctx.id)
   })
   return n0
index e1da7d1570b5490e8123020c6f7f7bcb07550067..865e46319ea43cacfb7aa6590a6de28fe42e8779 100644 (file)
@@ -1,13 +1,13 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
 exports[`v-html > should convert v-html to innerHTML 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setHtml as _setHtml } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setHtml as _setHtml } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setHtml(n1, undefined, _ctx.code)
   })
   return n0
@@ -15,13 +15,13 @@ export function render(_ctx) {
 `;
 
 exports[`v-html > should raise error and ignore children when v-html is present 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setHtml as _setHtml } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setHtml as _setHtml } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setHtml(n1, undefined, _ctx.test)
   })
   return n0
index c1f85b96a2165d7c8e27ebff1b7b1c33508fa2d3..efea7f51d08c22f9940b1d5cdc54d1155aa7e232 100644 (file)
@@ -13,13 +13,13 @@ export function render(_ctx) {
 `;
 
 exports[`v-on > dynamic arg 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, on as _on } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, on as _on } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _on(n1, _ctx.event, (...args) => (_ctx.handler && _ctx.handler(...args)))
   })
   return n0
@@ -109,13 +109,13 @@ export function render(_ctx) {
 `;
 
 exports[`v-on > should transform click.middle 2`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _on(n1, (_ctx.event) === "click" ? "mouseup" : (_ctx.event), _withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["middle"]))
   })
   return n0
@@ -135,31 +135,19 @@ export function render(_ctx) {
 `;
 
 exports[`v-on > should transform click.right 2`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _on(n1, (_ctx.event) === "click" ? "contextmenu" : (_ctx.event), _withKeys(_withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["right"]), ["right"]))
   })
   return n0
 }"
 `;
 
-exports[`v-on > should wrap as function if expression is inline statement 1`] = `
-"import { template as _template, children as _children, on as _on } from 'vue/vapor';
-
-export function render(_ctx) {
-  const t0 = _template("<div></div>")
-  const n0 = t0()
-  const { 0: [n1],} = _children(n0)
-  _on(n1, "click", (...args) => (_ctx.i++ && _ctx.i++(...args)))
-  return n0
-}"
-`;
-
 exports[`v-on > should wrap keys guard for keyboard events or dynamic events 1`] = `
 "import { template as _template, children as _children, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
 
index 8beed48c7747b6fce228feff2ba119b421d3508e..0ae00ce44c78c23958e6bb16c52e9b257211f14d 100644 (file)
@@ -1,13 +1,13 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
 exports[`v-text > should convert v-text to textContent 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setText as _setText } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n1, undefined, _ctx.str)
   })
   return n0
@@ -15,13 +15,13 @@ export function render(_ctx) {
 `;
 
 exports[`v-text > should raise error and ignore children when v-text is present 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setText as _setText } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _watchEffect(() => {
+  _renderEffect(() => {
     _setText(n1, undefined, _ctx.test)
   })
   return n0
index b6ef80f309c9c215b4e81f2e267412c8ab765d33..3c7932684b3b997ef9abaf4691ba7625620a6929 100644 (file)
@@ -210,7 +210,7 @@ describe('compiler v-bind', () => {
     })
 
     expect(code).matchSnapshot()
-    expect(code).contains('watchEffect')
+    expect(code).contains('renderEffect')
     expect(code).contains('_setAttr(n1, "fooBar", undefined, _ctx.fooBar)')
   })
 
@@ -230,7 +230,7 @@ describe('compiler v-bind', () => {
     })
 
     expect(code).matchSnapshot()
-    expect(code).contains('watchEffect')
+    expect(code).contains('renderEffect')
     expect(code).contains(
       `_setAttr(n1, _camelize(_ctx.foo), undefined, _ctx.id)`,
     )
index 691c31467741ae041608a0880f4f3ba4aee9a236..08a73b43016ce7d68ce4818eb69cc29070f3abfc 100644 (file)
@@ -102,7 +102,7 @@ describe('v-on', () => {
     const { code, ir } = compileWithVOn(`<div v-on:[event]="handler"/>`)
 
     expect(ir.vaporHelpers).contains('on')
-    expect(ir.vaporHelpers).contains('watchEffect')
+    expect(ir.vaporHelpers).contains('renderEffect')
     expect(ir.helpers.size).toBe(0)
     expect(ir.operation).toEqual([])
 
index 7d0cea4e6bc5eb099a6b51248f36a0f7d8f1008c..6caf9a0d06443d4c206c5f151d57fdaff3824bbe 100644 (file)
@@ -293,7 +293,7 @@ export function generate(
       }
 
       for (const { operations } of ir.effect) {
-        pushNewline(`${vaporHelper('watchEffect')}(() => {`)
+        pushNewline(`${vaporHelper('renderEffect')}(() => {`)
         withIndent(() => {
           for (const operation of operations) {
             genOperation(operation, ctx)
index 741a816b102edce98957748cb5c985fcb37c6e37..228c59cb2f9df6a839811540dff6ede64899cc92 100644 (file)
@@ -1,28 +1,12 @@
-import { EffectScope, type Ref, ref } from '@vue/reactivity'
+import type { Ref } from '@vue/reactivity'
 import {
+  EffectScope,
+  nextTick,
   onEffectCleanup,
+  ref,
   watchEffect,
-  watchPostEffect,
   watchSyncEffect,
-} from '../src/apiWatch'
-import { nextTick } from '../src/scheduler'
-import { defineComponent } from 'vue'
-import { render } from '../src/render'
-import { template } from '../src/template'
-
-let host: HTMLElement
-
-const initHost = () => {
-  host = document.createElement('div')
-  host.setAttribute('id', 'host')
-  document.body.appendChild(host)
-}
-beforeEach(() => {
-  initHost()
-})
-afterEach(() => {
-  host.remove()
-})
+} from '../src'
 
 describe('watchEffect and onEffectCleanup', () => {
   test('basic', async () => {
@@ -93,71 +77,4 @@ describe('watchEffect and onEffectCleanup', () => {
     await nextTick()
     expect(dummy).toBe(15)
   })
-
-  test('scheduling order', async () => {
-    const calls: string[] = []
-
-    const demo = defineComponent({
-      setup() {
-        const source = ref(0)
-        const change = () => source.value++
-
-        watchPostEffect(() => {
-          const current = source.value
-          calls.push(`post ${current}`)
-          onEffectCleanup(() => calls.push(`post cleanup ${current}`))
-        })
-        watchEffect(() => {
-          const current = source.value
-          calls.push(`pre ${current}`)
-          onEffectCleanup(() => calls.push(`pre cleanup ${current}`))
-        })
-        watchSyncEffect(() => {
-          const current = source.value
-          calls.push(`sync ${current}`)
-          onEffectCleanup(() => calls.push(`sync cleanup ${current}`))
-        })
-        const __returned__ = { source, change }
-        Object.defineProperty(__returned__, '__isScriptSetup', {
-          enumerable: false,
-          value: true,
-        })
-        return __returned__
-      },
-    })
-
-    demo.render = (_ctx: any) => {
-      const t0 = template('<div></div>')
-      watchEffect(() => {
-        const current = _ctx.source
-        calls.push(`render ${current}`)
-        onEffectCleanup(() => calls.push(`render cleanup ${current}`))
-      })
-      return t0()
-    }
-
-    const instance = render(demo as any, {}, '#host')
-    const { change } = instance.setupState as any
-
-    expect(calls).toEqual(['pre 0', 'sync 0', 'render 0'])
-    calls.length = 0
-
-    await nextTick()
-    expect(calls).toEqual(['post 0'])
-    calls.length = 0
-
-    change()
-    expect(calls).toEqual(['sync cleanup 0', 'sync 1'])
-    calls.length = 0
-
-    await nextTick()
-    expect(calls).toEqual([
-      'pre cleanup 0',
-      'pre 1',
-      'render cleanup 0',
-      'render 1',
-      'post cleanup 0',
-      'post 1',
-    ])
-  })
 })
diff --git a/packages/runtime-vapor/__tests__/renderWatch.spec.ts b/packages/runtime-vapor/__tests__/renderWatch.spec.ts
new file mode 100644 (file)
index 0000000..0d43ad9
--- /dev/null
@@ -0,0 +1,133 @@
+import { defineComponent } from 'vue'
+import {
+  nextTick,
+  onEffectCleanup,
+  ref,
+  render,
+  renderEffect,
+  renderWatch,
+  template,
+  watchEffect,
+  watchPostEffect,
+  watchSyncEffect,
+} from '../src'
+
+let host: HTMLElement
+
+const initHost = () => {
+  host = document.createElement('div')
+  host.setAttribute('id', 'host')
+  document.body.appendChild(host)
+}
+beforeEach(() => {
+  initHost()
+})
+afterEach(() => {
+  host.remove()
+})
+
+describe('renderWatch', () => {
+  test('effect', async () => {
+    let dummy: any
+    const source = ref(0)
+    renderEffect(() => {
+      dummy = source.value
+    })
+    await nextTick()
+    expect(dummy).toBe(0)
+    source.value++
+    await nextTick()
+    expect(dummy).toBe(1)
+  })
+
+  test('watch', async () => {
+    let dummy: any
+    const source = ref(0)
+    renderWatch(source, () => {
+      dummy = source.value
+    })
+    await nextTick()
+    expect(dummy).toBe(undefined)
+    source.value++
+    await nextTick()
+    expect(dummy).toBe(1)
+  })
+
+  test('scheduling order', async () => {
+    const calls: string[] = []
+
+    const demo = defineComponent({
+      setup() {
+        const source = ref(0)
+        const renderSource = ref(0)
+        const change = () => source.value++
+        const changeRender = () => renderSource.value++
+
+        watchPostEffect(() => {
+          const current = source.value
+          calls.push(`post ${current}`)
+          onEffectCleanup(() => calls.push(`post cleanup ${current}`))
+        })
+        watchEffect(() => {
+          const current = source.value
+          calls.push(`pre ${current}`)
+          onEffectCleanup(() => calls.push(`pre cleanup ${current}`))
+        })
+        watchSyncEffect(() => {
+          const current = source.value
+          calls.push(`sync ${current}`)
+          onEffectCleanup(() => calls.push(`sync cleanup ${current}`))
+        })
+        const __returned__ = { source, change, renderSource, changeRender }
+        Object.defineProperty(__returned__, '__isScriptSetup', {
+          enumerable: false,
+          value: true,
+        })
+        return __returned__
+      },
+    })
+
+    demo.render = (_ctx: any) => {
+      const t0 = template('<div></div>')
+      renderEffect(() => {
+        const current = _ctx.renderSource
+        calls.push(`renderEffect ${current}`)
+        onEffectCleanup(() => calls.push(`renderEffect cleanup ${current}`))
+      })
+      renderWatch(
+        () => _ctx.renderSource,
+        (value) => {
+          calls.push(`renderWatch ${value}`)
+          onEffectCleanup(() => calls.push(`renderWatch cleanup ${value}`))
+        },
+      )
+      return t0()
+    }
+
+    const instance = render(demo as any, {}, '#host')
+    const { change, changeRender } = instance.setupState as any
+
+    expect(calls).toEqual(['pre 0', 'sync 0', 'renderEffect 0'])
+    calls.length = 0
+
+    await nextTick()
+    expect(calls).toEqual(['post 0'])
+    calls.length = 0
+
+    changeRender()
+    change()
+    expect(calls).toEqual(['sync cleanup 0', 'sync 1'])
+    calls.length = 0
+
+    await nextTick()
+    expect(calls).toEqual([
+      'pre cleanup 0',
+      'pre 1',
+      'renderEffect cleanup 0',
+      'renderEffect 1',
+      'renderWatch 1',
+      'post cleanup 0',
+      'post 1',
+    ])
+  })
+})
index ca03b9f875e94e5b378714afdc4c190c2555cda1..2c7cd8f6321f9d90480d5995924dc07278959697 100644 (file)
@@ -1,40 +1,21 @@
 import {
+  type BaseWatchErrorCodes,
+  type BaseWatchOptions,
   type ComputedRef,
   type DebuggerOptions,
-  type EffectScheduler,
-  ReactiveEffect,
-  ReactiveFlags,
   type Ref,
+  baseWatch,
   getCurrentScope,
-  isReactive,
-  isRef,
 } from '@vue/reactivity'
-import {
-  EMPTY_OBJ,
-  NOOP,
-  extend,
-  hasChanged,
-  isArray,
-  isFunction,
-  isMap,
-  isObject,
-  isPlainObject,
-  isSet,
-  remove,
-} from '@vue/shared'
+import { EMPTY_OBJ, NOOP, extend, isFunction, remove } from '@vue/shared'
 import { currentInstance } from './component'
 import {
-  type Scheduler,
-  type SchedulerJob,
-  getVaporSchedulerByFlushMode,
-  vaporPostScheduler,
-  vaporSyncScheduler,
+  type SchedulerFactory,
+  createVaporPostScheduler,
+  createVaporPreScheduler,
+  createVaporSyncScheduler,
 } from './scheduler'
-import {
-  VaporErrorCodes,
-  callWithAsyncErrorHandling,
-  callWithErrorHandling,
-} from './errorHandling'
+import { handleError as handleErrorWithInstance } from './errorHandling'
 import { warn } from './warning'
 
 export type WatchEffect = (onCleanup: OnCleanup) => void
@@ -76,10 +57,9 @@ export type WatchStopHandle = () => void
 // Simple effect.
 export function watchEffect(
   effect: WatchEffect,
-  options: WatchOptionsBase = EMPTY_OBJ,
+  options?: WatchOptionsBase,
 ): WatchStopHandle {
-  const { flush } = options
-  return doWatch(effect, null, getVaporSchedulerByFlushMode(flush), options)
+  return doWatch(effect, null, options)
 }
 
 export function watchPostEffect(
@@ -89,7 +69,6 @@ export function watchPostEffect(
   return doWatch(
     effect,
     null,
-    vaporPostScheduler,
     __DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
   )
 }
@@ -101,16 +80,19 @@ export function watchSyncEffect(
   return doWatch(
     effect,
     null,
-    vaporSyncScheduler,
     __DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
   )
 }
 
-// initial value for watchers to trigger on undefined initial values
-const INITIAL_WATCHER_VALUE = {}
-
 type MultiWatchSources = (WatchSource<unknown> | object)[]
 
+// overload: single source + cb
+export function watch<T, Immediate extends Readonly<boolean> = false>(
+  source: WatchSource<T>,
+  cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
+  options?: WatchOptions<Immediate>,
+): WatchStopHandle
+
 // overload: array of multiple sources + cb
 export function watch<
   T extends MultiWatchSources,
@@ -133,13 +115,6 @@ export function watch<
   options?: WatchOptions<Immediate>,
 ): WatchStopHandle
 
-// overload: single source + cb
-export function watch<T, Immediate extends Readonly<boolean> = false>(
-  source: WatchSource<T>,
-  cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
-  options?: WatchOptions<Immediate>,
-): WatchStopHandle
-
 // overload: watching reactive object w/ cb
 export function watch<
   T extends object,
@@ -154,7 +129,7 @@ export function watch<
 export function watch<T = any, Immediate extends Readonly<boolean> = false>(
   source: T | WatchSource<T>,
   cb: any,
-  options: WatchOptions<Immediate> = EMPTY_OBJ,
+  options?: WatchOptions<Immediate>,
 ): WatchStopHandle {
   if (__DEV__ && !isFunction(cb)) {
     warn(
@@ -163,46 +138,33 @@ export function watch<T = any, Immediate extends Readonly<boolean> = false>(
         `supports \`watch(source, cb, options?) signature.`,
     )
   }
-  const { flush } = options
-  return doWatch(
-    source as any,
-    cb,
-    getVaporSchedulerByFlushMode(flush),
-    options,
-  )
+  return doWatch(source as any, cb, options)
 }
 
-const cleanupMap: WeakMap<ReactiveEffect, (() => void)[]> = new WeakMap()
-let activeEffect: ReactiveEffect | undefined = undefined
-
-// TODO: extract it to the reactivity package
-export function onEffectCleanup(cleanupFn: () => void) {
-  if (activeEffect) {
-    const cleanups =
-      cleanupMap.get(activeEffect) ||
-      cleanupMap.set(activeEffect, []).get(activeEffect)!
-    cleanups.push(cleanupFn)
+function getScheduler(flush: WatchOptionsBase['flush']): SchedulerFactory {
+  if (flush === 'post') {
+    return createVaporPostScheduler
   }
-}
-
-export interface doWatchOptions<Immediate = boolean> extends DebuggerOptions {
-  immediate?: Immediate
-  deep?: boolean
-  once?: boolean
+  if (flush === 'sync') {
+    return createVaporSyncScheduler
+  }
+  // default: 'pre'
+  return createVaporPreScheduler
 }
 
 function doWatch(
   source: WatchSource | WatchSource[] | WatchEffect | object,
   cb: WatchCallback | null,
-  scheduler: Scheduler,
-  { immediate, deep, once, onTrack, onTrigger }: doWatchOptions = EMPTY_OBJ,
+  options: WatchOptions = EMPTY_OBJ,
 ): WatchStopHandle {
-  if (cb && once) {
-    const _cb = cb
-    cb = (...args) => {
-      _cb(...args)
-      unwatch()
-    }
+  const { immediate, deep, flush, once } = options
+
+  // TODO remove in 3.5
+  if (__DEV__ && deep !== void 0 && typeof deep === 'number') {
+    warn(
+      `watch() "deep" option with number value will be used as watch depth in future versions. ` +
+        `Please use a boolean instead to avoid potential breakage.`,
+    )
   }
 
   if (__DEV__ && !cb) {
@@ -226,214 +188,43 @@ function doWatch(
     }
   }
 
-  const warnInvalidSource = (s: unknown) => {
-    warn(
-      `Invalid watch source: `,
-      s,
-      `A watch source can only be a getter/effect function, a ref, ` +
-        `a reactive object, or an array of these types.`,
-    )
-  }
+  const extendOptions: BaseWatchOptions = {}
 
-  const instance =
-    getCurrentScope() === currentInstance?.scope ? currentInstance : null
-  // const instance = currentInstance
-  let getter: () => any
-  let forceTrigger = false
-  let isMultiSource = false
+  if (__DEV__) extendOptions.onWarn = warn
 
-  if (isRef(source)) {
-    getter = () => source.value
-  } else if (isReactive(source)) {
-    getter = () => source
-    deep = true
-  } else if (isArray(source)) {
-    getter = () =>
-      source.map((s) => {
-        if (isRef(s)) {
-          return s.value
-        } else if (isReactive(s)) {
-          return traverse(s)
-        } else if (isFunction(s)) {
-          return callWithErrorHandling(
-            s,
-            instance,
-            VaporErrorCodes.WATCH_GETTER,
-          )
-        } else {
-          __DEV__ && warnInvalidSource(s)
-        }
-      })
-  } else if (isFunction(source)) {
-    if (cb) {
-      // getter with cb
-      getter = () =>
-        callWithErrorHandling(source, instance, VaporErrorCodes.WATCH_GETTER)
-    } else {
-      // no cb -> simple effect
-      getter = () => {
-        if (instance && instance.isUnmounted) {
-          return
-        }
-        if (cleanup) {
-          cleanup()
-        }
-        const currentEffect = activeEffect
-        activeEffect = effect
-        try {
-          return callWithAsyncErrorHandling(
-            source,
-            instance,
-            VaporErrorCodes.WATCH_CALLBACK,
-            [onEffectCleanup],
-          )
-        } finally {
-          activeEffect = currentEffect
-        }
-      }
-    }
-  } else {
-    getter = NOOP
-    __DEV__ && warnInvalidSource(source)
-  }
-
-  if (cb && deep) {
-    const baseGetter = getter
-    getter = () => traverse(baseGetter())
-  }
-
-  // TODO: ssr
+  let ssrCleanup: (() => void)[] | undefined
+  // TODO: SSR
   // if (__SSR__ && isInSSRComponentSetup) {
+  //   if (flush === 'sync') {
+  //     const ctx = useSSRContext()!
+  //     ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
+  //   } else if (!cb || immediate) {
+  //     // immediately watch or watchEffect
+  //     extendOptions.once = true
+  //   } else {
+  //     // watch(source, cb)
+  //     return NOOP
+  //   }
   // }
 
-  let oldValue: any = isMultiSource
-    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
-    : INITIAL_WATCHER_VALUE
-  const job: SchedulerJob = () => {
-    if (!effect.active || !effect.dirty) {
-      return
-    }
-    if (cb) {
-      // watch(source, cb)
-      const newValue = effect.run()
-      if (
-        deep ||
-        forceTrigger ||
-        (isMultiSource
-          ? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))
-          : hasChanged(newValue, oldValue))
-      ) {
-        // cleanup before running cb again
-        if (cleanup) {
-          cleanup()
-        }
-        const currentEffect = activeEffect
-        activeEffect = effect
-        try {
-          callWithAsyncErrorHandling(
-            cb,
-            instance,
-            VaporErrorCodes.WATCH_CALLBACK,
-            [
-              newValue,
-              // pass undefined as the old value when it's changed for the first time
-              oldValue === INITIAL_WATCHER_VALUE
-                ? undefined
-                : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
-                  ? []
-                  : oldValue,
-              onEffectCleanup,
-            ],
-          )
-          oldValue = newValue
-        } finally {
-          activeEffect = currentEffect
-        }
-      }
-    } else {
-      // watchEffect
-      effect.run()
-    }
-  }
-
-  // important: mark the job as a watcher callback so that scheduler knows
-  // it is allowed to self-trigger (#1727)
-  job.allowRecurse = !!cb
-
-  let effectScheduler: EffectScheduler = () =>
-    scheduler({
-      effect,
-      job,
-      instance: instance,
-      isInit: false,
-    })
+  const instance = currentInstance
 
-  const effect = new ReactiveEffect(getter, NOOP, effectScheduler)
+  extendOptions.onError = (err: unknown, type: BaseWatchErrorCodes) =>
+    handleErrorWithInstance(err, instance, type)
+  extendOptions.scheduler = getScheduler(flush)(instance)
 
-  const cleanup = (effect.onStop = () => {
-    const cleanups = cleanupMap.get(effect)
-    if (cleanups) {
-      cleanups.forEach((cleanup) => cleanup())
-      cleanupMap.delete(effect)
-    }
-  })
+  let effect = baseWatch(source, cb, extend({}, options, extendOptions))
 
-  const unwatch = () => {
-    effect.stop()
-    if (instance && instance.scope) {
-      remove(instance.scope.effects!, effect)
-    }
-  }
-
-  if (__DEV__) {
-    effect.onTrack = onTrack
-    effect.onTrigger = onTrigger
-  }
-
-  // initial run
-  if (cb) {
-    if (immediate) {
-      job()
-    } else {
-      oldValue = effect.run()
-    }
-  } else {
-    scheduler({
-      effect,
-      job,
-      instance: instance,
-      isInit: true,
-    })
-  }
+  const scope = getCurrentScope()
+  const unwatch = !effect
+    ? NOOP
+    : () => {
+        effect!.stop()
+        if (scope) {
+          remove(scope.effects, effect)
+        }
+      }
 
-  // TODO: ssr
-  // if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
+  if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
   return unwatch
 }
-
-export function traverse(value: unknown, seen?: Set<unknown>) {
-  if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
-    return value
-  }
-  seen = seen || new Set()
-  if (seen.has(value)) {
-    return value
-  }
-  seen.add(value)
-  if (isRef(value)) {
-    traverse(value.value, seen)
-  } else if (isArray(value)) {
-    for (let i = 0; i < value.length; i++) {
-      traverse(value[i], seen)
-    }
-  } else if (isSet(value) || isMap(value)) {
-    value.forEach((v: any) => {
-      traverse(v, seen)
-    })
-  } else if (isPlainObject(value)) {
-    for (const key in value) {
-      traverse(value[key], seen)
-    }
-  }
-  return value
-}
index 0867d4bd949404c87a8cc441f7f1f57166f6b465..fa3954280fb5c4adb4adcbbf023509b34701577f 100644 (file)
@@ -7,16 +7,20 @@ import type { ComponentInternalInstance } from './component'
 import { isFunction, isPromise } from '@vue/shared'
 import { warn } from './warning'
 import { VaporLifecycleHooks } from './enums'
+import { BaseWatchErrorCodes } from '@vue/reactivity'
 
 // contexts where user provided function may be executed, in addition to
 // lifecycle hooks.
 export enum VaporErrorCodes {
   SETUP_FUNCTION,
   RENDER_FUNCTION,
-  WATCH_GETTER,
-  WATCH_CALLBACK,
-  WATCH_CLEANUP,
-  NATIVE_EVENT_HANDLER,
+  // The error codes for the watch have been transferred to the reactivity
+  // package along with baseWatch to maintain code compatibility. Hence,
+  // it is essential to keep these values unchanged.
+  // WATCH_GETTER,
+  // WATCH_CALLBACK,
+  // WATCH_CLEANUP,
+  NATIVE_EVENT_HANDLER = 5,
   COMPONENT_EVENT_HANDLER,
   VNODE_HOOK,
   DIRECTIVE_HOOK,
@@ -28,10 +32,12 @@ export enum VaporErrorCodes {
   SCHEDULER,
 }
 
-export const ErrorTypeStrings: Record<
-  VaporLifecycleHooks | VaporErrorCodes,
-  string
-> = {
+export type ErrorTypes =
+  | VaporLifecycleHooks
+  | VaporErrorCodes
+  | BaseWatchErrorCodes
+
+export const ErrorTypeStrings: Record<ErrorTypes, string> = {
   // [VaporLifecycleHooks.SERVER_PREFETCH]: 'serverPrefetch hook',
   [VaporLifecycleHooks.BEFORE_CREATE]: 'beforeCreate hook',
   [VaporLifecycleHooks.CREATED]: 'created hook',
@@ -48,9 +54,9 @@ export const ErrorTypeStrings: Record<
   [VaporLifecycleHooks.RENDER_TRIGGERED]: 'renderTriggered hook',
   [VaporErrorCodes.SETUP_FUNCTION]: 'setup function',
   [VaporErrorCodes.RENDER_FUNCTION]: 'render function',
-  [VaporErrorCodes.WATCH_GETTER]: 'watcher getter',
-  [VaporErrorCodes.WATCH_CALLBACK]: 'watcher callback',
-  [VaporErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
+  [BaseWatchErrorCodes.WATCH_GETTER]: 'watcher getter',
+  [BaseWatchErrorCodes.WATCH_CALLBACK]: 'watcher callback',
+  [BaseWatchErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
   [VaporErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
   [VaporErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
   [VaporErrorCodes.VNODE_HOOK]: 'vnode hook',
@@ -65,8 +71,6 @@ export const ErrorTypeStrings: Record<
     'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core',
 }
 
-export type ErrorTypes = VaporLifecycleHooks | VaporErrorCodes
-
 export function callWithErrorHandling(
   fn: Function,
   instance: ComponentInternalInstance | null,
index d34c9f483f17c0882b0519ed251ae982b2664661..c740912ef1e4af99ffe950777d4b9b362c08c35e 100644 (file)
@@ -29,6 +29,7 @@ export {
   // effect
   stop,
   ReactiveEffect,
+  onEffectCleanup,
   // effect scope
   effectScope,
   EffectScope,
@@ -39,6 +40,7 @@ export { withModifiers, withKeys } from '@vue/runtime-dom'
 
 export * from './on'
 export * from './render'
+export * from './renderWatch'
 export * from './template'
 export * from './scheduler'
 export * from './apiWatch'
diff --git a/packages/runtime-vapor/src/renderWatch.ts b/packages/runtime-vapor/src/renderWatch.ts
new file mode 100644 (file)
index 0000000..fd9385f
--- /dev/null
@@ -0,0 +1,55 @@
+import {
+  type BaseWatchErrorCodes,
+  type BaseWatchOptions,
+  baseWatch,
+  getCurrentScope,
+} from '@vue/reactivity'
+import { NOOP, remove } from '@vue/shared'
+import { currentInstance } from './component'
+import { createVaporRenderingScheduler } from './scheduler'
+import { handleError as handleErrorWithInstance } from './errorHandling'
+import { warn } from './warning'
+
+type WatchStopHandle = () => void
+
+export function renderEffect(effect: () => void): WatchStopHandle {
+  return doWatch(effect)
+}
+
+export function renderWatch(
+  source: any,
+  cb: (value: any, oldValue: any) => void,
+): WatchStopHandle {
+  return doWatch(source as any, cb)
+}
+
+function doWatch(source: any, cb?: any): WatchStopHandle {
+  const extendOptions: BaseWatchOptions = {}
+
+  if (__DEV__) extendOptions.onWarn = warn
+
+  // TODO: Life Cycle Hooks
+
+  // TODO: SSR
+  // if (__SSR__) {}
+
+  const instance =
+    getCurrentScope() === currentInstance?.scope ? currentInstance : null
+
+  extendOptions.onError = (err: unknown, type: BaseWatchErrorCodes) =>
+    handleErrorWithInstance(err, instance, type)
+  extendOptions.scheduler = createVaporRenderingScheduler(instance)
+
+  let effect = baseWatch(source, cb, extendOptions)
+
+  const unwatch = !effect
+    ? NOOP
+    : () => {
+        effect!.stop()
+        if (instance && instance.scope) {
+          remove(instance.scope.effects!, effect)
+        }
+      }
+
+  return unwatch
+}
index de511787e8b7d8c133ad66106ca5d905668d1710..2be470254941f0878115bae8602f891af5f79993 100644 (file)
@@ -1,6 +1,5 @@
-import type { ReactiveEffect } from '@vue/reactivity'
+import type { Scheduler } from '@vue/reactivity'
 import type { ComponentInternalInstance } from './component'
-import { getIsRendering } from '.'
 
 export interface SchedulerJob extends Function {
   id?: number
@@ -38,13 +37,6 @@ export type QueueEffect = (
   suspense: ComponentInternalInstance | null,
 ) => void
 
-export type Scheduler = (context: {
-  effect: ReactiveEffect
-  job: SchedulerJob
-  instance: ComponentInternalInstance | null
-  isInit: boolean
-}) => void
-
 let isFlushing = false
 let isFlushPending = false
 
@@ -205,64 +197,46 @@ const comparator = (a: SchedulerJob, b: SchedulerJob): number => {
   return diff
 }
 
-export function getVaporSchedulerByFlushMode(
-  flush?: 'pre' | 'post' | 'sync',
-): Scheduler {
-  if (flush === 'post') {
-    return vaporPostScheduler
-  }
-  if (flush === 'sync') {
-    return vaporSyncScheduler
-  }
-  if (getIsRendering()) {
-    return vaporRenderingScheduler
-  }
-  // default: 'pre'
-  return vaporPreScheduler
-}
+export type SchedulerFactory = (
+  instance: ComponentInternalInstance | null,
+) => Scheduler
 
-export const vaporSyncScheduler: Scheduler = ({ isInit, effect, job }) => {
-  if (isInit) {
-    effect.run()
-  } else {
-    job()
+export const createVaporSyncScheduler: SchedulerFactory =
+  (instance) => (job, effect, isInit) => {
+    if (isInit) {
+      effect.run()
+    } else {
+      job()
+    }
   }
-}
 
-export const vaporPreScheduler: Scheduler = ({
-  isInit,
-  effect,
-  instance,
-  job,
-}) => {
-  if (isInit) {
-    effect.run()
-  } else {
-    job.pre = true
-    if (instance) job.id = instance.uid
-    queueJob(job)
+export const createVaporPreScheduler: SchedulerFactory =
+  (instance) => (job, effect, isInit) => {
+    if (isInit) {
+      effect.run()
+    } else {
+      job.pre = true
+      if (instance) job.id = instance.uid
+      queueJob(job)
+    }
   }
-}
 
-export const vaporRenderingScheduler: Scheduler = ({
-  isInit,
-  effect,
-  instance,
-  job,
-}) => {
-  if (isInit) {
-    effect.run()
-  } else {
-    job.pre = false
-    if (instance) job.id = instance.uid
-    queueJob(job)
+export const createVaporRenderingScheduler: SchedulerFactory =
+  (instance) => (job, effect, isInit) => {
+    if (isInit) {
+      effect.run()
+    } else {
+      job.pre = false
+      if (instance) job.id = instance.uid
+      queueJob(job)
+    }
   }
-}
 
-export const vaporPostScheduler: Scheduler = ({ isInit, effect, job }) => {
-  if (isInit) {
-    queuePostRenderEffect(effect.run.bind(effect))
-  } else {
-    queuePostRenderEffect(job)
+export const createVaporPostScheduler: SchedulerFactory =
+  (instance) => (job, effect, isInit) => {
+    if (isInit) {
+      queuePostRenderEffect(effect.run.bind(effect))
+    } else {
+      queuePostRenderEffect(job)
+    }
   }
-}