feat: renderWatch / renderEffect (#86)
* 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>
This commit is contained in:
parent
bdf28de8e8
commit
fb4d9a1443
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)`,
|
||||
)
|
||||
|
|
|
@ -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([])
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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',
|
||||
])
|
||||
})
|
||||
})
|
|
@ -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
|
||||
const instance = currentInstance
|
||||
|
||||
extendOptions.onError = (err: unknown, type: BaseWatchErrorCodes) =>
|
||||
handleErrorWithInstance(err, instance, type)
|
||||
extendOptions.scheduler = getScheduler(flush)(instance)
|
||||
|
||||
let effect = baseWatch(source, cb, extend({}, options, extendOptions))
|
||||
|
||||
const scope = getCurrentScope()
|
||||
const unwatch = !effect
|
||||
? NOOP
|
||||
: () => {
|
||||
effect!.stop()
|
||||
if (scope) {
|
||||
remove(scope.effects, effect)
|
||||
}
|
||||
}
|
||||
} 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 effect = new ReactiveEffect(getter, NOOP, effectScheduler)
|
||||
|
||||
const cleanup = (effect.onStop = () => {
|
||||
const cleanups = cleanupMap.get(effect)
|
||||
if (cleanups) {
|
||||
cleanups.forEach((cleanup) => cleanup())
|
||||
cleanupMap.delete(effect)
|
||||
}
|
||||
})
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue