feat(runtime-vapor): `watchEffect`/`watch` and `onEffectCleanup` (#69)
This commit is contained in:
parent
597eae423b
commit
5b3027f0a9
|
@ -1,7 +1,7 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`compile > bindings 1`] = `
|
exports[`compile > bindings 1`] = `
|
||||||
"import { template as _template, children as _children, createTextNode as _createTextNode, insert as _insert, effect as _effect, setText as _setText } from 'vue/vapor';
|
"import { template as _template, children as _children, createTextNode as _createTextNode, insert as _insert, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div>count is <!>.</div>")
|
const t0 = _template("<div>count is <!>.</div>")
|
||||||
|
@ -9,7 +9,7 @@ export function render(_ctx) {
|
||||||
const { 0: [n3, { 1: [n2],}],} = _children(n0)
|
const { 0: [n3, { 1: [n2],}],} = _children(n0)
|
||||||
const n1 = _createTextNode(_ctx.count)
|
const n1 = _createTextNode(_ctx.count)
|
||||||
_insert(n1, n3, n2)
|
_insert(n1, n3, n2)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n1, undefined, _ctx.count)
|
_setText(n1, undefined, _ctx.count)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -121,7 +121,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compile > directives > v-pre > self-closing v-pre 1`] = `
|
exports[`compile > directives > v-pre > self-closing v-pre 1`] = `
|
||||||
"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, effect as _effect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
|
"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';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div><div><Comp></Comp></div>")
|
const t0 = _template("<div></div><div><Comp></Comp></div>")
|
||||||
|
@ -129,10 +129,10 @@ export function render(_ctx) {
|
||||||
const { 1: [n2],} = _children(n0)
|
const { 1: [n2],} = _children(n0)
|
||||||
const n1 = _createTextNode(_ctx.bar)
|
const n1 = _createTextNode(_ctx.bar)
|
||||||
_append(n2, n1)
|
_append(n2, n1)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n1, undefined, _ctx.bar)
|
_setText(n1, undefined, _ctx.bar)
|
||||||
})
|
})
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setAttr(n2, "id", undefined, _ctx.foo)
|
_setAttr(n2, "id", undefined, _ctx.foo)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -140,7 +140,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
|
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, effect as _effect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
|
"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';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div :id=\\"foo\\"><Comp></Comp>{{ bar }}</div><div><Comp></Comp></div>")
|
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 { 1: [n2],} = _children(n0)
|
||||||
const n1 = _createTextNode(_ctx.bar)
|
const n1 = _createTextNode(_ctx.bar)
|
||||||
_append(n2, n1)
|
_append(n2, n1)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n1, undefined, _ctx.bar)
|
_setText(n1, undefined, _ctx.bar)
|
||||||
})
|
})
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setAttr(n2, "id", undefined, _ctx.foo)
|
_setAttr(n2, "id", undefined, _ctx.foo)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -159,7 +159,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compile > dynamic root 1`] = `
|
exports[`compile > dynamic root 1`] = `
|
||||||
"import { fragment as _fragment, createTextNode as _createTextNode, append as _append, effect as _effect, setText as _setText } from 'vue/vapor';
|
"import { fragment as _fragment, createTextNode as _createTextNode, append as _append, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _fragment()
|
const t0 = _fragment()
|
||||||
|
@ -168,10 +168,10 @@ export function render(_ctx) {
|
||||||
const n1 = _createTextNode(1)
|
const n1 = _createTextNode(1)
|
||||||
const n2 = _createTextNode(2)
|
const n2 = _createTextNode(2)
|
||||||
_append(n0, n1, n2)
|
_append(n0, n1, n2)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n1, undefined, 1)
|
_setText(n1, undefined, 1)
|
||||||
})
|
})
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n2, undefined, 2)
|
_setText(n2, undefined, 2)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -179,7 +179,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compile > dynamic root nodes and interpolation 1`] = `
|
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, effect as _effect, 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, watchEffect as _watchEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<button>foo<!>foo</button>")
|
const t0 = _template("<button>foo<!>foo</button>")
|
||||||
|
@ -192,7 +192,7 @@ export function render(_ctx) {
|
||||||
_insert(n2, n4, n5)
|
_insert(n2, n4, n5)
|
||||||
_append(n4, n3)
|
_append(n4, n3)
|
||||||
_on(n4, "click", (...args) => (_ctx.handleClick && _ctx.handleClick(...args)))
|
_on(n4, "click", (...args) => (_ctx.handleClick && _ctx.handleClick(...args)))
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n1, undefined, _ctx.count)
|
_setText(n1, undefined, _ctx.count)
|
||||||
_setText(n2, undefined, _ctx.count)
|
_setText(n2, undefined, _ctx.count)
|
||||||
_setText(n3, undefined, _ctx.count)
|
_setText(n3, undefined, _ctx.count)
|
||||||
|
@ -207,7 +207,7 @@ exports[`compile > expression parsing > interpolation 1`] = `
|
||||||
const t0 = _fragment()
|
const t0 = _fragment()
|
||||||
|
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n0, undefined, a + b.value)
|
_setText(n0, undefined, a + b.value)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -219,7 +219,7 @@ exports[`compile > expression parsing > v-bind 1`] = `
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setAttr(n1, key.value+1, undefined, _unref(foo)[key.value+1]())
|
_setAttr(n1, key.value+1, undefined, _unref(foo)[key.value+1]())
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -237,7 +237,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compile > static + dynamic root 1`] = `
|
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, effect as _effect, 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, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("3<!>6<!>9")
|
const t0 = _template("3<!>6<!>9")
|
||||||
|
@ -255,28 +255,28 @@ export function render(_ctx) {
|
||||||
_insert([n3, n4], n0, n9)
|
_insert([n3, n4], n0, n9)
|
||||||
_insert([n5, n6], n0, n10)
|
_insert([n5, n6], n0, n10)
|
||||||
_append(n0, n7, n8)
|
_append(n0, n7, n8)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n1, undefined, 1)
|
_setText(n1, undefined, 1)
|
||||||
})
|
})
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n2, undefined, 2)
|
_setText(n2, undefined, 2)
|
||||||
})
|
})
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n3, undefined, 4)
|
_setText(n3, undefined, 4)
|
||||||
})
|
})
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n4, undefined, 5)
|
_setText(n4, undefined, 5)
|
||||||
})
|
})
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n5, undefined, 7)
|
_setText(n5, undefined, 7)
|
||||||
})
|
})
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n6, undefined, 8)
|
_setText(n6, undefined, 8)
|
||||||
})
|
})
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n7, undefined, 'A')
|
_setText(n7, undefined, 'A')
|
||||||
})
|
})
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n8, undefined, 'B')
|
_setText(n8, undefined, 'B')
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`compiler v-bind > .camel modifier 1`] = `
|
exports[`compiler v-bind > .camel modifier 1`] = `
|
||||||
"import { template as _template, children as _children, effect as _effect, setAttr as _setAttr } from 'vue/vapor';
|
"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setAttr(n1, "fooBar", undefined, _ctx.id)
|
_setAttr(n1, "fooBar", undefined, _ctx.id)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -21,7 +21,7 @@ export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setAttr(n1, _camelize(_ctx.foo), undefined, _ctx.id)
|
_setAttr(n1, _camelize(_ctx.foo), undefined, _ctx.id)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -29,13 +29,13 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler v-bind > .camel modifier w/ no expression 1`] = `
|
exports[`compiler v-bind > .camel modifier w/ no expression 1`] = `
|
||||||
"import { template as _template, children as _children, effect as _effect, setAttr as _setAttr } from 'vue/vapor';
|
"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setAttr(n1, "fooBar", undefined, _ctx.fooBar)
|
_setAttr(n1, "fooBar", undefined, _ctx.fooBar)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -43,13 +43,13 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler v-bind > basic 1`] = `
|
exports[`compiler v-bind > basic 1`] = `
|
||||||
"import { template as _template, children as _children, effect as _effect, setAttr as _setAttr } from 'vue/vapor';
|
"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setAttr(n1, "id", undefined, _ctx.id)
|
_setAttr(n1, "id", undefined, _ctx.id)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -57,13 +57,13 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler v-bind > dynamic arg 1`] = `
|
exports[`compiler v-bind > dynamic arg 1`] = `
|
||||||
"import { template as _template, children as _children, effect as _effect, setAttr as _setAttr } from 'vue/vapor';
|
"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setAttr(n1, _ctx.id, undefined, _ctx.id)
|
_setAttr(n1, _ctx.id, undefined, _ctx.id)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -71,13 +71,13 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler v-bind > no expression (shorthand) 1`] = `
|
exports[`compiler v-bind > no expression (shorthand) 1`] = `
|
||||||
"import { template as _template, children as _children, effect as _effect, setAttr as _setAttr } from 'vue/vapor';
|
"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setAttr(n1, "camel-case", undefined, _ctx.camelCase)
|
_setAttr(n1, "camel-case", undefined, _ctx.camelCase)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -85,13 +85,13 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler v-bind > no expression 1`] = `
|
exports[`compiler v-bind > no expression 1`] = `
|
||||||
"import { template as _template, children as _children, effect as _effect, setAttr as _setAttr } from 'vue/vapor';
|
"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setAttr(n1, "id", undefined, _ctx.id)
|
_setAttr(n1, "id", undefined, _ctx.id)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`v-html > should convert v-html to innerHTML 1`] = `
|
exports[`v-html > should convert v-html to innerHTML 1`] = `
|
||||||
"import { template as _template, children as _children, effect as _effect, setHtml as _setHtml } from 'vue/vapor';
|
"import { template as _template, children as _children, watchEffect as _watchEffect, setHtml as _setHtml } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setHtml(n1, undefined, _ctx.code)
|
_setHtml(n1, undefined, _ctx.code)
|
||||||
})
|
})
|
||||||
return n0
|
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`] = `
|
exports[`v-html > should raise error and ignore children when v-html is present 1`] = `
|
||||||
"import { template as _template, children as _children, effect as _effect, setHtml as _setHtml } from 'vue/vapor';
|
"import { template as _template, children as _children, watchEffect as _watchEffect, setHtml as _setHtml } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setHtml(n1, undefined, _ctx.test)
|
_setHtml(n1, undefined, _ctx.test)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
|
|
@ -13,13 +13,13 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > dynamic arg 1`] = `
|
exports[`v-on > dynamic arg 1`] = `
|
||||||
"import { template as _template, children as _children, effect as _effect, on as _on } from 'vue/vapor';
|
"import { template as _template, children as _children, watchEffect as _watchEffect, on as _on } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_on(n1, _ctx.event, (...args) => (_ctx.handler && _ctx.handler(...args)))
|
_on(n1, _ctx.event, (...args) => (_ctx.handler && _ctx.handler(...args)))
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -109,13 +109,13 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > should transform click.middle 2`] = `
|
exports[`v-on > should transform click.middle 2`] = `
|
||||||
"import { template as _template, children as _children, effect as _effect, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
|
"import { template as _template, children as _children, watchEffect as _watchEffect, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_on(n1, (_ctx.event) === "click" ? "mouseup" : (_ctx.event), _withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["middle"]))
|
_on(n1, (_ctx.event) === "click" ? "mouseup" : (_ctx.event), _withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["middle"]))
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
@ -135,13 +135,13 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > should transform click.right 2`] = `
|
exports[`v-on > should transform click.right 2`] = `
|
||||||
"import { template as _template, children as _children, effect as _effect, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
|
"import { template as _template, children as _children, watchEffect as _watchEffect, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_on(n1, (_ctx.event) === "click" ? "contextmenu" : (_ctx.event), _withKeys(_withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["right"]), ["right"]))
|
_on(n1, (_ctx.event) === "click" ? "contextmenu" : (_ctx.event), _withKeys(_withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["right"]), ["right"]))
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`v-text > should convert v-text to textContent 1`] = `
|
exports[`v-text > should convert v-text to textContent 1`] = `
|
||||||
"import { template as _template, children as _children, effect as _effect, setText as _setText } from 'vue/vapor';
|
"import { template as _template, children as _children, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n1, undefined, _ctx.str)
|
_setText(n1, undefined, _ctx.str)
|
||||||
})
|
})
|
||||||
return n0
|
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`] = `
|
exports[`v-text > should raise error and ignore children when v-text is present 1`] = `
|
||||||
"import { template as _template, children as _children, effect as _effect, setText as _setText } from 'vue/vapor';
|
"import { template as _template, children as _children, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_effect(() => {
|
_watchEffect(() => {
|
||||||
_setText(n1, undefined, _ctx.test)
|
_setText(n1, undefined, _ctx.test)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
|
|
@ -210,7 +210,7 @@ describe('compiler v-bind', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(code).matchSnapshot()
|
expect(code).matchSnapshot()
|
||||||
expect(code).contains('effect')
|
expect(code).contains('watchEffect')
|
||||||
expect(code).contains('_setAttr(n1, "fooBar", undefined, _ctx.fooBar)')
|
expect(code).contains('_setAttr(n1, "fooBar", undefined, _ctx.fooBar)')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -230,7 +230,7 @@ describe('compiler v-bind', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(code).matchSnapshot()
|
expect(code).matchSnapshot()
|
||||||
expect(code).contains('effect')
|
expect(code).contains('watchEffect')
|
||||||
expect(code).contains(
|
expect(code).contains(
|
||||||
`_setAttr(n1, _camelize(_ctx.foo), undefined, _ctx.id)`,
|
`_setAttr(n1, _camelize(_ctx.foo), undefined, _ctx.id)`,
|
||||||
)
|
)
|
||||||
|
|
|
@ -102,7 +102,7 @@ describe('v-on', () => {
|
||||||
const { code, ir } = compileWithVOn(`<div v-on:[event]="handler"/>`)
|
const { code, ir } = compileWithVOn(`<div v-on:[event]="handler"/>`)
|
||||||
|
|
||||||
expect(ir.vaporHelpers).contains('on')
|
expect(ir.vaporHelpers).contains('on')
|
||||||
expect(ir.vaporHelpers).contains('effect')
|
expect(ir.vaporHelpers).contains('watchEffect')
|
||||||
expect(ir.helpers.size).toBe(0)
|
expect(ir.helpers.size).toBe(0)
|
||||||
expect(ir.operation).toEqual([])
|
expect(ir.operation).toEqual([])
|
||||||
|
|
||||||
|
|
|
@ -293,7 +293,7 @@ export function generate(
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const { operations } of ir.effect) {
|
for (const { operations } of ir.effect) {
|
||||||
pushNewline(`${vaporHelper('effect')}(() => {`)
|
pushNewline(`${vaporHelper('watchEffect')}(() => {`)
|
||||||
withIndent(() => {
|
withIndent(() => {
|
||||||
for (const operation of operations) {
|
for (const operation of operations) {
|
||||||
genOperation(operation, ctx)
|
genOperation(operation, ctx)
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
import { EffectScope, Ref, ref } from '@vue/reactivity'
|
||||||
|
import {
|
||||||
|
onEffectCleanup,
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('watchEffect and onEffectCleanup', () => {
|
||||||
|
test('basic', async () => {
|
||||||
|
let dummy = 0
|
||||||
|
let source: Ref<number>
|
||||||
|
const scope = new EffectScope()
|
||||||
|
|
||||||
|
scope.run(() => {
|
||||||
|
source = ref(0)
|
||||||
|
watchEffect((onCleanup) => {
|
||||||
|
source.value
|
||||||
|
|
||||||
|
onCleanup(() => (dummy += 2))
|
||||||
|
onEffectCleanup(() => (dummy += 3))
|
||||||
|
onEffectCleanup(() => (dummy += 5))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await nextTick()
|
||||||
|
expect(dummy).toBe(0)
|
||||||
|
|
||||||
|
scope.run(() => {
|
||||||
|
source.value++
|
||||||
|
})
|
||||||
|
await nextTick()
|
||||||
|
expect(dummy).toBe(10)
|
||||||
|
|
||||||
|
scope.run(() => {
|
||||||
|
source.value++
|
||||||
|
})
|
||||||
|
await nextTick()
|
||||||
|
expect(dummy).toBe(20)
|
||||||
|
|
||||||
|
scope.stop()
|
||||||
|
await nextTick()
|
||||||
|
expect(dummy).toBe(30)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('nested call to watchEffect', async () => {
|
||||||
|
let dummy = 0
|
||||||
|
let source: Ref<number>
|
||||||
|
let double: Ref<number>
|
||||||
|
const scope = new EffectScope()
|
||||||
|
|
||||||
|
scope.run(() => {
|
||||||
|
source = ref(0)
|
||||||
|
double = ref(0)
|
||||||
|
watchEffect(() => {
|
||||||
|
double.value = source.value * 2
|
||||||
|
onEffectCleanup(() => (dummy += 2))
|
||||||
|
})
|
||||||
|
watchSyncEffect(() => {
|
||||||
|
double.value
|
||||||
|
onEffectCleanup(() => (dummy += 3))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await nextTick()
|
||||||
|
expect(dummy).toBe(0)
|
||||||
|
|
||||||
|
scope.run(() => source.value++)
|
||||||
|
await nextTick()
|
||||||
|
expect(dummy).toBe(5)
|
||||||
|
|
||||||
|
scope.run(() => source.value++)
|
||||||
|
await nextTick()
|
||||||
|
expect(dummy).toBe(10)
|
||||||
|
|
||||||
|
scope.stop()
|
||||||
|
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.proxy 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',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,11 +1,11 @@
|
||||||
import {
|
import {
|
||||||
template,
|
template,
|
||||||
children,
|
children,
|
||||||
effect,
|
|
||||||
setText,
|
setText,
|
||||||
render,
|
render,
|
||||||
ref,
|
ref,
|
||||||
unmountComponent,
|
unmountComponent,
|
||||||
|
watchEffect,
|
||||||
} from '../src'
|
} from '../src'
|
||||||
import { afterEach, beforeEach, describe, expect } from 'vitest'
|
import { afterEach, beforeEach, describe, expect } from 'vitest'
|
||||||
import { defineComponent } from '@vue/runtime-core'
|
import { defineComponent } from '@vue/runtime-core'
|
||||||
|
@ -33,7 +33,7 @@ describe('component', () => {
|
||||||
const {
|
const {
|
||||||
0: [n1],
|
0: [n1],
|
||||||
} = children(n0)
|
} = children(n0)
|
||||||
effect(() => {
|
watchEffect(() => {
|
||||||
setText(n1, void 0, count.value)
|
setText(n1, void 0, count.value)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
|
|
@ -0,0 +1,439 @@
|
||||||
|
import {
|
||||||
|
ComputedRef,
|
||||||
|
Ref,
|
||||||
|
isReactive,
|
||||||
|
isRef,
|
||||||
|
ReactiveEffect,
|
||||||
|
EffectScheduler,
|
||||||
|
DebuggerOptions,
|
||||||
|
getCurrentScope,
|
||||||
|
ReactiveFlags,
|
||||||
|
} from '@vue/reactivity'
|
||||||
|
import {
|
||||||
|
EMPTY_OBJ,
|
||||||
|
NOOP,
|
||||||
|
extend,
|
||||||
|
hasChanged,
|
||||||
|
isArray,
|
||||||
|
isFunction,
|
||||||
|
isMap,
|
||||||
|
isObject,
|
||||||
|
isPlainObject,
|
||||||
|
isSet,
|
||||||
|
remove,
|
||||||
|
} from '@vue/shared'
|
||||||
|
import { currentInstance } from './component'
|
||||||
|
import {
|
||||||
|
type Scheduler,
|
||||||
|
getVaporSchedulerByFlushMode,
|
||||||
|
vaporPostScheduler,
|
||||||
|
vaporSyncScheduler,
|
||||||
|
SchedulerJob,
|
||||||
|
} from './scheduler'
|
||||||
|
import {
|
||||||
|
VaporErrorCodes,
|
||||||
|
callWithAsyncErrorHandling,
|
||||||
|
callWithErrorHandling,
|
||||||
|
} from './errorHandling'
|
||||||
|
import { warn } from './warning'
|
||||||
|
|
||||||
|
export type WatchEffect = (onCleanup: OnCleanup) => void
|
||||||
|
|
||||||
|
export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
|
||||||
|
|
||||||
|
export type WatchCallback<V = any, OV = any> = (
|
||||||
|
value: V,
|
||||||
|
oldValue: OV,
|
||||||
|
onCleanup: OnCleanup,
|
||||||
|
) => any
|
||||||
|
|
||||||
|
type MapSources<T, Immediate> = {
|
||||||
|
[K in keyof T]: T[K] extends WatchSource<infer V>
|
||||||
|
? Immediate extends true
|
||||||
|
? V | undefined
|
||||||
|
: V
|
||||||
|
: T[K] extends object
|
||||||
|
? Immediate extends true
|
||||||
|
? T[K] | undefined
|
||||||
|
: T[K]
|
||||||
|
: never
|
||||||
|
}
|
||||||
|
|
||||||
|
type OnCleanup = (cleanupFn: () => void) => void
|
||||||
|
|
||||||
|
export interface WatchOptionsBase extends DebuggerOptions {
|
||||||
|
flush?: 'pre' | 'post' | 'sync'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
|
||||||
|
immediate?: Immediate
|
||||||
|
deep?: boolean
|
||||||
|
once?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WatchStopHandle = () => void
|
||||||
|
|
||||||
|
// Simple effect.
|
||||||
|
export function watchEffect(
|
||||||
|
effect: WatchEffect,
|
||||||
|
options: WatchOptionsBase = EMPTY_OBJ,
|
||||||
|
): WatchStopHandle {
|
||||||
|
const { flush } = options
|
||||||
|
return doWatch(effect, null, getVaporSchedulerByFlushMode(flush), options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function watchPostEffect(
|
||||||
|
effect: WatchEffect,
|
||||||
|
options?: DebuggerOptions,
|
||||||
|
) {
|
||||||
|
return doWatch(
|
||||||
|
effect,
|
||||||
|
null,
|
||||||
|
vaporPostScheduler,
|
||||||
|
__DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function watchSyncEffect(
|
||||||
|
effect: WatchEffect,
|
||||||
|
options?: DebuggerOptions,
|
||||||
|
) {
|
||||||
|
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: array of multiple sources + cb
|
||||||
|
export function watch<
|
||||||
|
T extends MultiWatchSources,
|
||||||
|
Immediate extends Readonly<boolean> = false,
|
||||||
|
>(
|
||||||
|
sources: [...T],
|
||||||
|
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
||||||
|
options?: WatchOptions<Immediate>,
|
||||||
|
): WatchStopHandle
|
||||||
|
|
||||||
|
// overload: multiple sources w/ `as const`
|
||||||
|
// watch([foo, bar] as const, () => {})
|
||||||
|
// somehow [...T] breaks when the type is readonly
|
||||||
|
export function watch<
|
||||||
|
T extends Readonly<MultiWatchSources>,
|
||||||
|
Immediate extends Readonly<boolean> = false,
|
||||||
|
>(
|
||||||
|
source: T,
|
||||||
|
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
||||||
|
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,
|
||||||
|
Immediate extends Readonly<boolean> = false,
|
||||||
|
>(
|
||||||
|
source: T,
|
||||||
|
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
|
||||||
|
options?: WatchOptions<Immediate>,
|
||||||
|
): WatchStopHandle
|
||||||
|
|
||||||
|
// implementation
|
||||||
|
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
|
||||||
|
source: T | WatchSource<T>,
|
||||||
|
cb: any,
|
||||||
|
options: WatchOptions<Immediate> = EMPTY_OBJ,
|
||||||
|
): WatchStopHandle {
|
||||||
|
if (__DEV__ && !isFunction(cb)) {
|
||||||
|
warn(
|
||||||
|
`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
|
||||||
|
`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
|
||||||
|
`supports \`watch(source, cb, options?) signature.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const { flush } = options
|
||||||
|
return doWatch(
|
||||||
|
source as any,
|
||||||
|
cb,
|
||||||
|
getVaporSchedulerByFlushMode(flush),
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface doWatchOptions<Immediate = boolean> extends DebuggerOptions {
|
||||||
|
immediate?: Immediate
|
||||||
|
deep?: boolean
|
||||||
|
once?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function doWatch(
|
||||||
|
source: WatchSource | WatchSource[] | WatchEffect | object,
|
||||||
|
cb: WatchCallback | null,
|
||||||
|
scheduler: Scheduler,
|
||||||
|
{ immediate, deep, once, onTrack, onTrigger }: doWatchOptions = EMPTY_OBJ,
|
||||||
|
): WatchStopHandle {
|
||||||
|
if (cb && once) {
|
||||||
|
const _cb = cb
|
||||||
|
cb = (...args) => {
|
||||||
|
_cb(...args)
|
||||||
|
unwatch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__DEV__ && !cb) {
|
||||||
|
if (immediate !== undefined) {
|
||||||
|
warn(
|
||||||
|
`watch() "immediate" option is only respected when using the ` +
|
||||||
|
`watch(source, callback, options?) signature.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (deep !== undefined) {
|
||||||
|
warn(
|
||||||
|
`watch() "deep" option is only respected when using the ` +
|
||||||
|
`watch(source, callback, options?) signature.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (once !== undefined) {
|
||||||
|
warn(
|
||||||
|
`watch() "once" option is only respected when using the ` +
|
||||||
|
`watch(source, callback, options?) signature.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 instance =
|
||||||
|
getCurrentScope() === currentInstance?.scope ? currentInstance : null
|
||||||
|
// const instance = currentInstance
|
||||||
|
let getter: () => any
|
||||||
|
let forceTrigger = false
|
||||||
|
let isMultiSource = false
|
||||||
|
|
||||||
|
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
|
||||||
|
// if (__SSR__ && isInSSRComponentSetup) {
|
||||||
|
// }
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
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
|
||||||
|
}
|
|
@ -35,6 +35,8 @@ export interface ComponentInternalInstance {
|
||||||
component: FunctionalComponent | ObjectComponent
|
component: FunctionalComponent | ObjectComponent
|
||||||
propsOptions: NormalizedPropsOptions
|
propsOptions: NormalizedPropsOptions
|
||||||
|
|
||||||
|
parent: ComponentInternalInstance | null
|
||||||
|
|
||||||
// TODO: type
|
// TODO: type
|
||||||
proxy: Data | null
|
proxy: Data | null
|
||||||
|
|
||||||
|
@ -50,7 +52,7 @@ export interface ComponentInternalInstance {
|
||||||
get isUnmounted(): boolean
|
get isUnmounted(): boolean
|
||||||
isUnmountedRef: Ref<boolean>
|
isUnmountedRef: Ref<boolean>
|
||||||
isMountedRef: Ref<boolean>
|
isMountedRef: Ref<boolean>
|
||||||
// TODO: registory of provides, appContext, lifecycles, ...
|
// TODO: registory of provides, lifecycles, ...
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -136,6 +138,9 @@ export const createComponentInstance = (
|
||||||
scope: new EffectScope(true /* detached */)!,
|
scope: new EffectScope(true /* detached */)!,
|
||||||
component,
|
component,
|
||||||
|
|
||||||
|
// TODO: registory of parent
|
||||||
|
parent: null,
|
||||||
|
|
||||||
// resolved props and emits options
|
// resolved props and emits options
|
||||||
propsOptions: normalizePropsOptions(component),
|
propsOptions: normalizePropsOptions(component),
|
||||||
// emitsOptions: normalizeEmitsOptions(type, appContext), // TODO:
|
// emitsOptions: normalizeEmitsOptions(type, appContext), // TODO:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { isFunction } from '@vue/shared'
|
import { isFunction } from '@vue/shared'
|
||||||
import { currentInstance, type ComponentInternalInstance } from './component'
|
import { currentInstance, type ComponentInternalInstance } from './component'
|
||||||
import { effect } from './scheduler'
|
import { watchEffect } from './apiWatch'
|
||||||
|
|
||||||
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
|
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ export function withDirectives<T extends Node>(
|
||||||
|
|
||||||
callDirectiveHook(node, binding, 'created')
|
callDirectiveHook(node, binding, 'created')
|
||||||
|
|
||||||
effect(() => {
|
watchEffect(() => {
|
||||||
if (!instance.isMountedRef.value) return
|
if (!instance.isMountedRef.value) return
|
||||||
callDirectiveHook(node, binding, 'updated')
|
callDirectiveHook(node, binding, 'updated')
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
// These codes originate from a file of the same name in runtime-core,
|
||||||
|
// duplicated during Vapor's early development to ensure its independence.
|
||||||
|
// The ultimate aim is to uncouple this replicated code and
|
||||||
|
// facilitate its shared use between two runtimes.
|
||||||
|
|
||||||
|
import { VaporLifecycleHooks } from './apiLifecycle'
|
||||||
|
import { type ComponentInternalInstance } from './component'
|
||||||
|
import { isFunction, isPromise } from '@vue/shared'
|
||||||
|
import { warn } from './warning'
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
COMPONENT_EVENT_HANDLER,
|
||||||
|
VNODE_HOOK,
|
||||||
|
DIRECTIVE_HOOK,
|
||||||
|
TRANSITION_HOOK,
|
||||||
|
APP_ERROR_HANDLER,
|
||||||
|
APP_WARN_HANDLER,
|
||||||
|
FUNCTION_REF,
|
||||||
|
ASYNC_COMPONENT_LOADER,
|
||||||
|
SCHEDULER,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ErrorTypeStrings: Record<
|
||||||
|
VaporLifecycleHooks | VaporErrorCodes,
|
||||||
|
string
|
||||||
|
> = {
|
||||||
|
// [VaporLifecycleHooks.SERVER_PREFETCH]: 'serverPrefetch hook',
|
||||||
|
[VaporLifecycleHooks.BEFORE_CREATE]: 'beforeCreate hook',
|
||||||
|
[VaporLifecycleHooks.CREATED]: 'created hook',
|
||||||
|
[VaporLifecycleHooks.BEFORE_MOUNT]: 'beforeMount hook',
|
||||||
|
[VaporLifecycleHooks.MOUNTED]: 'mounted hook',
|
||||||
|
[VaporLifecycleHooks.BEFORE_UPDATE]: 'beforeUpdate hook',
|
||||||
|
[VaporLifecycleHooks.UPDATED]: 'updated',
|
||||||
|
[VaporLifecycleHooks.BEFORE_UNMOUNT]: 'beforeUnmount hook',
|
||||||
|
[VaporLifecycleHooks.UNMOUNTED]: 'unmounted hook',
|
||||||
|
[VaporLifecycleHooks.ACTIVATED]: 'activated hook',
|
||||||
|
[VaporLifecycleHooks.DEACTIVATED]: 'deactivated hook',
|
||||||
|
[VaporLifecycleHooks.ERROR_CAPTURED]: 'errorCaptured hook',
|
||||||
|
[VaporLifecycleHooks.RENDER_TRACKED]: 'renderTracked hook',
|
||||||
|
[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',
|
||||||
|
[VaporErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
|
||||||
|
[VaporErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
|
||||||
|
[VaporErrorCodes.VNODE_HOOK]: 'vnode hook',
|
||||||
|
[VaporErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
|
||||||
|
[VaporErrorCodes.TRANSITION_HOOK]: 'transition hook',
|
||||||
|
[VaporErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
|
||||||
|
[VaporErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
|
||||||
|
[VaporErrorCodes.FUNCTION_REF]: 'ref function',
|
||||||
|
[VaporErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
|
||||||
|
[VaporErrorCodes.SCHEDULER]:
|
||||||
|
'scheduler flush. This is likely a Vue internals bug. ' +
|
||||||
|
'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,
|
||||||
|
type: ErrorTypes,
|
||||||
|
args?: unknown[],
|
||||||
|
) {
|
||||||
|
let res
|
||||||
|
try {
|
||||||
|
res = args ? fn(...args) : fn()
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err, instance, type)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
export function callWithAsyncErrorHandling(
|
||||||
|
fn: Function | Function[],
|
||||||
|
instance: ComponentInternalInstance | null,
|
||||||
|
type: ErrorTypes,
|
||||||
|
args?: unknown[],
|
||||||
|
): any[] {
|
||||||
|
if (isFunction(fn)) {
|
||||||
|
const res = callWithErrorHandling(fn, instance, type, args)
|
||||||
|
if (res && isPromise(res)) {
|
||||||
|
res.catch((err) => {
|
||||||
|
handleError(err, instance, type)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = []
|
||||||
|
for (let i = 0; i < fn.length; i++) {
|
||||||
|
values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleError(
|
||||||
|
err: unknown,
|
||||||
|
instance: ComponentInternalInstance | null,
|
||||||
|
type: ErrorTypes,
|
||||||
|
throwInDev = true,
|
||||||
|
) {
|
||||||
|
if (instance) {
|
||||||
|
let cur = instance.parent
|
||||||
|
// the exposed instance is the render proxy to keep it consistent with 2.x
|
||||||
|
const exposedInstance = ('proxy' in instance && instance.proxy) || null
|
||||||
|
// in production the hook receives only the error code
|
||||||
|
const errorInfo = __DEV__
|
||||||
|
? ErrorTypeStrings[type]
|
||||||
|
: `https://vuejs.org/errors/#runtime-${type}`
|
||||||
|
while (cur) {
|
||||||
|
const errorCapturedHooks = 'ec' in cur ? cur.ec : null
|
||||||
|
if (errorCapturedHooks) {
|
||||||
|
for (let i = 0; i < errorCapturedHooks.length; i++) {
|
||||||
|
if (
|
||||||
|
errorCapturedHooks[i](err, exposedInstance, errorInfo) === false
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cur = cur.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: need appContext interface
|
||||||
|
// app-level handling
|
||||||
|
// const appErrorHandler = instance.appContext?.config.errorHandler
|
||||||
|
// if (appErrorHandler) {
|
||||||
|
// callWithErrorHandling(
|
||||||
|
// appErrorHandler,
|
||||||
|
// null,
|
||||||
|
// ErrorCodes.APP_ERROR_HANDLER,
|
||||||
|
// [err, exposedInstance, errorInfo],
|
||||||
|
// )
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
logError(err, type, throwInDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
function logError(err: unknown, type: ErrorTypes, throwInDev = true) {
|
||||||
|
if (__DEV__) {
|
||||||
|
const info = ErrorTypeStrings[type]
|
||||||
|
warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
|
||||||
|
// crash in dev by default so it's more noticeable
|
||||||
|
if (throwInDev) {
|
||||||
|
throw err
|
||||||
|
} else if (!__TEST__) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// recover in prod to reduce the impact on end-user
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,6 +41,7 @@ export * from './on'
|
||||||
export * from './render'
|
export * from './render'
|
||||||
export * from './template'
|
export * from './template'
|
||||||
export * from './scheduler'
|
export * from './scheduler'
|
||||||
|
export * from './apiWatch'
|
||||||
export * from './directive'
|
export * from './directive'
|
||||||
export * from './dom'
|
export * from './dom'
|
||||||
export * from './directives/vShow'
|
export * from './directives/vShow'
|
||||||
|
|
|
@ -17,6 +17,11 @@ export type ParentBlock = ParentNode | Node[]
|
||||||
export type Fragment = { nodes: Block; anchor: Node }
|
export type Fragment = { nodes: Block; anchor: Node }
|
||||||
export type BlockFn = (props: any, ctx: any) => Block
|
export type BlockFn = (props: any, ctx: any) => Block
|
||||||
|
|
||||||
|
let isRenderingActivity = false
|
||||||
|
export function getIsRendering() {
|
||||||
|
return isRenderingActivity
|
||||||
|
}
|
||||||
|
|
||||||
export function render(
|
export function render(
|
||||||
comp: Component,
|
comp: Component,
|
||||||
props: Data,
|
props: Data,
|
||||||
|
@ -53,7 +58,13 @@ export function mountComponent(
|
||||||
let block: Block | null = null
|
let block: Block | null = null
|
||||||
if (state && '__isScriptSetup' in state) {
|
if (state && '__isScriptSetup' in state) {
|
||||||
instance.setupState = proxyRefs(state)
|
instance.setupState = proxyRefs(state)
|
||||||
block = component.render(instance.proxy)
|
const currentlyRenderingActivity = isRenderingActivity
|
||||||
|
isRenderingActivity = true
|
||||||
|
try {
|
||||||
|
block = component.render(instance.proxy)
|
||||||
|
} finally {
|
||||||
|
isRenderingActivity = currentlyRenderingActivity
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
block = state as Block
|
block = state as Block
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,268 @@
|
||||||
import { ReactiveEffect } from '@vue/reactivity'
|
import { ReactiveEffect } from '@vue/reactivity'
|
||||||
|
import { ComponentInternalInstance } from './component'
|
||||||
|
import { getIsRendering } from '.'
|
||||||
|
|
||||||
const p = Promise.resolve()
|
export interface SchedulerJob extends Function {
|
||||||
|
id?: number
|
||||||
|
pre?: boolean
|
||||||
|
active?: boolean
|
||||||
|
computed?: boolean
|
||||||
|
/**
|
||||||
|
* Indicates whether the effect is allowed to recursively trigger itself
|
||||||
|
* when managed by the scheduler.
|
||||||
|
*
|
||||||
|
* By default, a job cannot trigger itself because some built-in method calls,
|
||||||
|
* e.g. Array.prototype.push actually performs reads as well (#1740) which
|
||||||
|
* can lead to confusing infinite loops.
|
||||||
|
* The allowed cases are component update functions and watch callbacks.
|
||||||
|
* Component update functions may update child component props, which in turn
|
||||||
|
* trigger flush: "pre" watch callbacks that mutates state that the parent
|
||||||
|
* relies on (#1801). Watch callbacks doesn't track its dependencies so if it
|
||||||
|
* triggers itself again, it's likely intentional and it is the user's
|
||||||
|
* responsibility to perform recursive state mutation that eventually
|
||||||
|
* stabilizes (#1727).
|
||||||
|
*/
|
||||||
|
allowRecurse?: boolean
|
||||||
|
/**
|
||||||
|
* Attached by renderer.ts when setting up a component's render effect
|
||||||
|
* Used to obtain component information when reporting max recursive updates.
|
||||||
|
* dev only.
|
||||||
|
*/
|
||||||
|
ownerInstance?: ComponentInternalInstance
|
||||||
|
}
|
||||||
|
|
||||||
let queued: any[] | undefined
|
export type SchedulerJobs = SchedulerJob | SchedulerJob[]
|
||||||
|
|
||||||
function queue(fn: any) {
|
export type QueueEffect = (
|
||||||
if (!queued) {
|
cb: SchedulerJobs,
|
||||||
queued = [fn]
|
suspense: ComponentInternalInstance | null,
|
||||||
p.then(flush)
|
) => void
|
||||||
|
|
||||||
|
export type Scheduler = (context: {
|
||||||
|
effect: ReactiveEffect
|
||||||
|
job: SchedulerJob
|
||||||
|
instance: ComponentInternalInstance | null
|
||||||
|
isInit: boolean
|
||||||
|
}) => void
|
||||||
|
|
||||||
|
let isFlushing = false
|
||||||
|
let isFlushPending = false
|
||||||
|
|
||||||
|
// TODO: The queues in Vapor need to be merged with the queues in Core.
|
||||||
|
// this is a temporary solution, the ultimate goal is to support
|
||||||
|
// the mixed use of vapor components and default components.
|
||||||
|
const queue: SchedulerJob[] = []
|
||||||
|
let flushIndex = 0
|
||||||
|
|
||||||
|
// TODO: The queues in Vapor need to be merged with the queues in Core.
|
||||||
|
// this is a temporary solution, the ultimate goal is to support
|
||||||
|
// the mixed use of vapor components and default components.
|
||||||
|
const pendingPostFlushCbs: SchedulerJob[] = []
|
||||||
|
let activePostFlushCbs: SchedulerJob[] | null = null
|
||||||
|
let postFlushIndex = 0
|
||||||
|
|
||||||
|
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
|
||||||
|
let currentFlushPromise: Promise<void> | null = null
|
||||||
|
|
||||||
|
function queueJob(job: SchedulerJob) {
|
||||||
|
if (
|
||||||
|
!queue.length ||
|
||||||
|
!queue.includes(
|
||||||
|
job,
|
||||||
|
isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (job.id == null) {
|
||||||
|
queue.push(job)
|
||||||
|
} else {
|
||||||
|
queue.splice(findInsertionIndex(job.id), 0, job)
|
||||||
|
}
|
||||||
|
queueFlush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function queuePostRenderEffect(cb: SchedulerJob) {
|
||||||
|
if (
|
||||||
|
!activePostFlushCbs ||
|
||||||
|
!activePostFlushCbs.includes(
|
||||||
|
cb,
|
||||||
|
cb.allowRecurse ? postFlushIndex + 1 : postFlushIndex,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
pendingPostFlushCbs.push(cb)
|
||||||
|
}
|
||||||
|
queueFlush()
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueFlush() {
|
||||||
|
if (!isFlushing && !isFlushPending) {
|
||||||
|
isFlushPending = true
|
||||||
|
currentFlushPromise = resolvedPromise.then(flushJobs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushPostFlushCbs() {
|
||||||
|
if (!pendingPostFlushCbs.length) return
|
||||||
|
|
||||||
|
const deduped = [...new Set(pendingPostFlushCbs)]
|
||||||
|
pendingPostFlushCbs.length = 0
|
||||||
|
|
||||||
|
// #1947 already has active queue, nested flushPostFlushCbs call
|
||||||
|
if (activePostFlushCbs) {
|
||||||
|
activePostFlushCbs.push(...deduped)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
activePostFlushCbs = deduped
|
||||||
|
|
||||||
|
activePostFlushCbs.sort((a, b) => getId(a) - getId(b))
|
||||||
|
|
||||||
|
for (
|
||||||
|
postFlushIndex = 0;
|
||||||
|
postFlushIndex < activePostFlushCbs.length;
|
||||||
|
postFlushIndex++
|
||||||
|
) {
|
||||||
|
activePostFlushCbs[postFlushIndex]()
|
||||||
|
}
|
||||||
|
activePostFlushCbs = null
|
||||||
|
postFlushIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: dev mode and checkRecursiveUpdates
|
||||||
|
function flushJobs() {
|
||||||
|
isFlushPending = false
|
||||||
|
isFlushing = true
|
||||||
|
|
||||||
|
// Sort queue before flush.
|
||||||
|
// This ensures that:
|
||||||
|
// 1. Components are updated from parent to child. (because parent is always
|
||||||
|
// created before the child so its render effect will have smaller
|
||||||
|
// priority number)
|
||||||
|
// 2. If a component is unmounted during a parent component's update,
|
||||||
|
// its update can be skipped.
|
||||||
|
queue.sort(comparator)
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < queue!.length; i++) {
|
||||||
|
queue![i]()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
flushIndex = 0
|
||||||
|
queue.length = 0
|
||||||
|
|
||||||
|
flushPostFlushCbs()
|
||||||
|
|
||||||
|
isFlushing = false
|
||||||
|
currentFlushPromise = null
|
||||||
|
// some postFlushCb queued jobs!
|
||||||
|
// keep flushing until it drains.
|
||||||
|
if (queue.length || pendingPostFlushCbs.length) {
|
||||||
|
flushJobs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nextTick<T = void, R = void>(
|
||||||
|
this: T,
|
||||||
|
fn?: (this: T) => R,
|
||||||
|
): Promise<Awaited<R>> {
|
||||||
|
const p = currentFlushPromise || resolvedPromise
|
||||||
|
return fn ? p.then(this ? fn.bind(this) : fn) : p
|
||||||
|
}
|
||||||
|
|
||||||
|
// #2768
|
||||||
|
// Use binary-search to find a suitable position in the queue,
|
||||||
|
// so that the queue maintains the increasing order of job's id,
|
||||||
|
// which can prevent the job from being skipped and also can avoid repeated patching.
|
||||||
|
function findInsertionIndex(id: number) {
|
||||||
|
// the start index should be `flushIndex + 1`
|
||||||
|
let start = flushIndex + 1
|
||||||
|
let end = queue.length
|
||||||
|
|
||||||
|
while (start < end) {
|
||||||
|
const middle = (start + end) >>> 1
|
||||||
|
const middleJob = queue[middle]
|
||||||
|
const middleJobId = getId(middleJob)
|
||||||
|
if (middleJobId < id || (middleJobId === id && middleJob.pre)) {
|
||||||
|
start = middle + 1
|
||||||
|
} else {
|
||||||
|
end = middle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
|
||||||
|
const getId = (job: SchedulerJob): number =>
|
||||||
|
job.id == null ? Infinity : job.id
|
||||||
|
|
||||||
|
const comparator = (a: SchedulerJob, b: SchedulerJob): number => {
|
||||||
|
const diff = getId(a) - getId(b)
|
||||||
|
if (diff === 0) {
|
||||||
|
if (a.pre && !b.pre) return -1
|
||||||
|
if (b.pre && !a.pre) return 1
|
||||||
|
}
|
||||||
|
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 const vaporSyncScheduler: Scheduler = ({ isInit, effect, job }) => {
|
||||||
|
if (isInit) {
|
||||||
|
effect.run()
|
||||||
} else {
|
} else {
|
||||||
queued.push(fn)
|
job()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function flush() {
|
export const vaporPreScheduler: Scheduler = ({
|
||||||
for (let i = 0; i < queued!.length; i++) {
|
isInit,
|
||||||
queued![i]()
|
effect,
|
||||||
|
instance,
|
||||||
|
job,
|
||||||
|
}) => {
|
||||||
|
if (isInit) {
|
||||||
|
effect.run()
|
||||||
|
} else {
|
||||||
|
job.pre = true
|
||||||
|
if (instance) job.id = instance.uid
|
||||||
|
queueJob(job)
|
||||||
}
|
}
|
||||||
queued = undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const nextTick = (fn?: any) => (fn ? p.then(fn) : p)
|
export const vaporRenderingScheduler: Scheduler = ({
|
||||||
|
isInit,
|
||||||
export function effect(fn: any) {
|
effect,
|
||||||
let run: () => void
|
instance,
|
||||||
const e = new ReactiveEffect(fn, () => queue(run))
|
job,
|
||||||
run = e.run.bind(e)
|
}) => {
|
||||||
run()
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function warn(msg: string, ...args: any[]) {
|
||||||
|
console.warn(`[Vue warn] ${msg}`, ...args)
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import {
|
||||||
on,
|
on,
|
||||||
ref,
|
ref,
|
||||||
template,
|
template,
|
||||||
effect,
|
watchEffect,
|
||||||
setText,
|
setText,
|
||||||
render as renderComponent // TODO:
|
render as renderComponent // TODO:
|
||||||
} from '@vue/vapor'
|
} from '@vue/vapor'
|
||||||
|
@ -35,7 +35,7 @@ export default {
|
||||||
0: [n1]
|
0: [n1]
|
||||||
} = children(n0)
|
} = children(n0)
|
||||||
on(n1, 'click', _ctx.handleClick)
|
on(n1, 'click', _ctx.handleClick)
|
||||||
effect(() => {
|
watchEffect(() => {
|
||||||
setText(n1, void 0, _ctx.count)
|
setText(n1, void 0, _ctx.count)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ const child = {
|
||||||
const {
|
const {
|
||||||
0: [n1]
|
0: [n1]
|
||||||
} = children(n0)
|
} = children(n0)
|
||||||
effect(() => {
|
watchEffect(() => {
|
||||||
setText(n1, void 0, _ctx.count + ' * 2 = ' + _ctx.inlineDouble)
|
setText(n1, void 0, _ctx.count + ' * 2 = ' + _ctx.inlineDouble)
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onEffectCleanup, ref, watch, watchEffect, watchPostEffect, watchSyncEffect } from 'vue/vapor'
|
||||||
|
|
||||||
|
const source = ref(0)
|
||||||
|
const add = () => source.value++
|
||||||
|
|
||||||
|
watchPostEffect(() => {
|
||||||
|
const current = source.value
|
||||||
|
console.log('post', current)
|
||||||
|
onEffectCleanup(() => console.log('cleanup post', current))
|
||||||
|
})
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
const current = source.value
|
||||||
|
console.log('pre', current)
|
||||||
|
onEffectCleanup(() => console.log('cleanup pre', current))
|
||||||
|
})
|
||||||
|
|
||||||
|
watchSyncEffect(() => {
|
||||||
|
const current = source.value
|
||||||
|
console.log('sync', current)
|
||||||
|
onEffectCleanup(() => console.log('cleanup sync', current))
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(source, (value, oldValue) => {
|
||||||
|
console.log('sync watch', value, 'oldValue:', oldValue)
|
||||||
|
onEffectCleanup(() => console.log('cleanup sync watch', value))
|
||||||
|
})
|
||||||
|
|
||||||
|
const onUpdate = (arg: any) => {
|
||||||
|
const current = source.value
|
||||||
|
console.log('render', current)
|
||||||
|
onEffectCleanup(() => console.log('cleanup render', current))
|
||||||
|
return arg
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>Please check the console</p>
|
||||||
|
<div>
|
||||||
|
<button @click="add">
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
|
|
||||||
|
<span>{{ onUpdate(source) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.red {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
color-scheme: dark;
|
||||||
|
background-color: #000;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue