mirror of https://github.com/vuejs/core.git
refactor(runtime-vapor): `renderEffect` based on `ReactiveEffect` + remove `renderWatch` (#155)
This commit is contained in:
parent
46761880e9
commit
64e83689a0
|
@ -1,11 +1,10 @@
|
||||||
|
import { onEffectCleanup } from '@vue/reactivity'
|
||||||
import {
|
import {
|
||||||
nextTick,
|
nextTick,
|
||||||
onBeforeUpdate,
|
onBeforeUpdate,
|
||||||
onUpdated,
|
onUpdated,
|
||||||
onWatcherCleanup,
|
|
||||||
ref,
|
ref,
|
||||||
renderEffect,
|
renderEffect,
|
||||||
renderWatch,
|
|
||||||
template,
|
template,
|
||||||
watchEffect,
|
watchEffect,
|
||||||
watchPostEffect,
|
watchPostEffect,
|
||||||
|
@ -31,8 +30,8 @@ const createDemo = (setupFn: () => any, renderFn: (ctx: any) => any) =>
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('renderWatch', () => {
|
describe('renderEffect', () => {
|
||||||
test('effect', async () => {
|
test('basic', async () => {
|
||||||
let dummy: any
|
let dummy: any
|
||||||
const source = ref(0)
|
const source = ref(0)
|
||||||
renderEffect(() => {
|
renderEffect(() => {
|
||||||
|
@ -58,26 +57,6 @@ describe('renderWatch', () => {
|
||||||
expect(dummy).toBe(3)
|
expect(dummy).toBe(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('watch', async () => {
|
|
||||||
let dummy: any
|
|
||||||
const source = ref(0)
|
|
||||||
renderWatch(source, () => {
|
|
||||||
dummy = source.value
|
|
||||||
})
|
|
||||||
await nextTick()
|
|
||||||
expect(dummy).toBe(undefined)
|
|
||||||
|
|
||||||
source.value++
|
|
||||||
expect(dummy).toBe(undefined)
|
|
||||||
await nextTick()
|
|
||||||
expect(dummy).toBe(1)
|
|
||||||
|
|
||||||
source.value++
|
|
||||||
expect(dummy).toBe(1)
|
|
||||||
await nextTick()
|
|
||||||
expect(dummy).toBe(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('should run with the scheduling order', async () => {
|
test('should run with the scheduling order', async () => {
|
||||||
const calls: string[] = []
|
const calls: string[] = []
|
||||||
|
|
||||||
|
@ -101,17 +80,17 @@ describe('renderWatch', () => {
|
||||||
watchPostEffect(() => {
|
watchPostEffect(() => {
|
||||||
const current = source.value
|
const current = source.value
|
||||||
calls.push(`post ${current}`)
|
calls.push(`post ${current}`)
|
||||||
onWatcherCleanup(() => calls.push(`post cleanup ${current}`))
|
onEffectCleanup(() => calls.push(`post cleanup ${current}`))
|
||||||
})
|
})
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
const current = source.value
|
const current = source.value
|
||||||
calls.push(`pre ${current}`)
|
calls.push(`pre ${current}`)
|
||||||
onWatcherCleanup(() => calls.push(`pre cleanup ${current}`))
|
onEffectCleanup(() => calls.push(`pre cleanup ${current}`))
|
||||||
})
|
})
|
||||||
watchSyncEffect(() => {
|
watchSyncEffect(() => {
|
||||||
const current = source.value
|
const current = source.value
|
||||||
calls.push(`sync ${current}`)
|
calls.push(`sync ${current}`)
|
||||||
onWatcherCleanup(() => calls.push(`sync cleanup ${current}`))
|
onEffectCleanup(() => calls.push(`sync cleanup ${current}`))
|
||||||
})
|
})
|
||||||
return { source, change, renderSource, changeRender }
|
return { source, change, renderSource, changeRender }
|
||||||
},
|
},
|
||||||
|
@ -121,15 +100,8 @@ describe('renderWatch', () => {
|
||||||
renderEffect(() => {
|
renderEffect(() => {
|
||||||
const current = _ctx.renderSource
|
const current = _ctx.renderSource
|
||||||
calls.push(`renderEffect ${current}`)
|
calls.push(`renderEffect ${current}`)
|
||||||
onWatcherCleanup(() => calls.push(`renderEffect cleanup ${current}`))
|
onEffectCleanup(() => calls.push(`renderEffect cleanup ${current}`))
|
||||||
})
|
})
|
||||||
renderWatch(
|
|
||||||
() => _ctx.renderSource,
|
|
||||||
value => {
|
|
||||||
calls.push(`renderWatch ${value}`)
|
|
||||||
onWatcherCleanup(() => calls.push(`renderWatch cleanup ${value}`))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
).render()
|
).render()
|
||||||
const { change, changeRender } = instance.setupState as any
|
const { change, changeRender } = instance.setupState as any
|
||||||
|
@ -151,7 +123,6 @@ describe('renderWatch', () => {
|
||||||
'beforeUpdate 1',
|
'beforeUpdate 1',
|
||||||
'renderEffect cleanup 0',
|
'renderEffect cleanup 0',
|
||||||
'renderEffect 1',
|
'renderEffect 1',
|
||||||
'renderWatch 1',
|
|
||||||
'post cleanup 0',
|
'post cleanup 0',
|
||||||
'post 1',
|
'post 1',
|
||||||
'updated 1',
|
'updated 1',
|
||||||
|
@ -172,8 +143,6 @@ describe('renderWatch', () => {
|
||||||
'beforeUpdate 2',
|
'beforeUpdate 2',
|
||||||
'renderEffect cleanup 1',
|
'renderEffect cleanup 1',
|
||||||
'renderEffect 2',
|
'renderEffect 2',
|
||||||
'renderWatch cleanup 1',
|
|
||||||
'renderWatch 2',
|
|
||||||
'post cleanup 1',
|
'post cleanup 1',
|
||||||
'post 2',
|
'post 2',
|
||||||
'updated 2',
|
'updated 2',
|
|
@ -1,7 +1,7 @@
|
||||||
import { type EffectScope, effectScope, isReactive } from '@vue/reactivity'
|
import { type EffectScope, effectScope, isReactive } from '@vue/reactivity'
|
||||||
import { isArray, isObject, isString } from '@vue/shared'
|
import { isArray, isObject, isString } from '@vue/shared'
|
||||||
import { createComment, createTextNode, insert, remove } from './dom/element'
|
import { createComment, createTextNode, insert, remove } from './dom/element'
|
||||||
import { renderEffect } from './renderWatch'
|
import { renderEffect } from './renderEffect'
|
||||||
import { type Block, type Fragment, fragmentKey } from './apiRender'
|
import { type Block, type Fragment, fragmentKey } from './apiRender'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { renderWatch } from './renderWatch'
|
import { renderEffect } from './renderEffect'
|
||||||
import { type Block, type Fragment, fragmentKey } from './apiRender'
|
import { type Block, type Fragment, fragmentKey } from './apiRender'
|
||||||
import { type EffectScope, effectScope } from '@vue/reactivity'
|
import { type EffectScope, effectScope } from '@vue/reactivity'
|
||||||
import { createComment, createTextNode, insert, remove } from './dom/element'
|
import { createComment, createTextNode, insert, remove } from './dom/element'
|
||||||
|
@ -12,6 +12,8 @@ export const createIf = (
|
||||||
b2?: BlockFn,
|
b2?: BlockFn,
|
||||||
// hydrationNode?: Node,
|
// hydrationNode?: Node,
|
||||||
): Fragment => {
|
): Fragment => {
|
||||||
|
let newValue: any
|
||||||
|
let oldValue: any
|
||||||
let branch: BlockFn | undefined
|
let branch: BlockFn | undefined
|
||||||
let parent: ParentNode | undefined | null
|
let parent: ParentNode | undefined | null
|
||||||
let block: Block | undefined
|
let block: Block | undefined
|
||||||
|
@ -29,15 +31,14 @@ export const createIf = (
|
||||||
// setCurrentHydrationNode(hydrationNode!)
|
// setCurrentHydrationNode(hydrationNode!)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
renderWatch(
|
renderEffect(() => {
|
||||||
() => !!condition(),
|
if ((newValue = !!condition()) !== oldValue) {
|
||||||
value => {
|
|
||||||
parent ||= anchor.parentNode
|
parent ||= anchor.parentNode
|
||||||
if (block) {
|
if (block) {
|
||||||
scope!.stop()
|
scope!.stop()
|
||||||
remove(block, parent!)
|
remove(block, parent!)
|
||||||
}
|
}
|
||||||
if ((branch = value ? b1 : b2)) {
|
if ((branch = (oldValue = newValue) ? b1 : b2)) {
|
||||||
scope = effectScope()
|
scope = effectScope()
|
||||||
fragment.nodes = block = scope.run(branch)!
|
fragment.nodes = block = scope.run(branch)!
|
||||||
parent && insert(block, parent, anchor)
|
parent && insert(block, parent, anchor)
|
||||||
|
@ -45,9 +46,8 @@ export const createIf = (
|
||||||
scope = block = undefined
|
scope = block = undefined
|
||||||
fragment.nodes = []
|
fragment.nodes = []
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
{ immediate: true },
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: SSR
|
// TODO: SSR
|
||||||
// if (isHydrating) {
|
// if (isHydrating) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { NOOP, isFunction } from '@vue/shared'
|
import { isFunction } from '@vue/shared'
|
||||||
import { type ComponentInternalInstance, currentInstance } from './component'
|
import { type ComponentInternalInstance, currentInstance } from './component'
|
||||||
import { pauseTracking, resetTracking } from '@vue/reactivity'
|
import { pauseTracking, resetTracking, traverse } from '@vue/reactivity'
|
||||||
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
||||||
import { renderWatch } from './renderWatch'
|
import { renderEffect } from './renderEffect'
|
||||||
|
|
||||||
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
|
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
|
||||||
|
|
||||||
|
@ -100,8 +100,12 @@ export function withDirectives<T extends Node>(
|
||||||
|
|
||||||
// register source
|
// register source
|
||||||
if (source) {
|
if (source) {
|
||||||
// callback will be overridden by middleware
|
if (dir.deep) {
|
||||||
renderWatch(source, NOOP, { deep: dir.deep })
|
const deep = dir.deep === true ? undefined : dir.deep
|
||||||
|
const baseSource = source
|
||||||
|
source = () => traverse(baseSource(), deep)
|
||||||
|
}
|
||||||
|
renderEffect(source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ export {
|
||||||
type FunctionalComponent,
|
type FunctionalComponent,
|
||||||
type SetupFn,
|
type SetupFn,
|
||||||
} from './component'
|
} from './component'
|
||||||
export { renderEffect, renderWatch } from './renderWatch'
|
export { renderEffect } from './renderEffect'
|
||||||
export {
|
export {
|
||||||
watch,
|
watch,
|
||||||
watchEffect,
|
watchEffect,
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { EffectFlags, ReactiveEffect, type SchedulerJob } from '@vue/reactivity'
|
||||||
|
import { invokeArrayFns } from '@vue/shared'
|
||||||
|
import { getCurrentInstance, setCurrentInstance } from './component'
|
||||||
|
import { queueJob, queuePostRenderEffect } from './scheduler'
|
||||||
|
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
||||||
|
import { invokeDirectiveHook } from './directives'
|
||||||
|
|
||||||
|
export function renderEffect(cb: () => void) {
|
||||||
|
const instance = getCurrentInstance()
|
||||||
|
|
||||||
|
let effect: ReactiveEffect
|
||||||
|
|
||||||
|
const job: SchedulerJob = () => {
|
||||||
|
if (!(effect.flags & EffectFlags.ACTIVE) || !effect.dirty) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance?.isMounted && !instance.isUpdating) {
|
||||||
|
instance.isUpdating = true
|
||||||
|
|
||||||
|
const { bu, u, dirs } = instance
|
||||||
|
// beforeUpdate hook
|
||||||
|
if (bu) {
|
||||||
|
invokeArrayFns(bu)
|
||||||
|
}
|
||||||
|
if (dirs) {
|
||||||
|
invokeDirectiveHook(instance, 'beforeUpdate')
|
||||||
|
}
|
||||||
|
|
||||||
|
effect.run()
|
||||||
|
|
||||||
|
queuePostRenderEffect(() => {
|
||||||
|
instance.isUpdating = false
|
||||||
|
if (dirs) {
|
||||||
|
invokeDirectiveHook(instance, 'updated')
|
||||||
|
}
|
||||||
|
// updated hook
|
||||||
|
if (u) {
|
||||||
|
queuePostRenderEffect(u)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
effect.run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
effect = new ReactiveEffect(() => {
|
||||||
|
const reset = instance && setCurrentInstance(instance)
|
||||||
|
callWithAsyncErrorHandling(cb, instance, VaporErrorCodes.RENDER_FUNCTION)
|
||||||
|
reset?.()
|
||||||
|
})
|
||||||
|
|
||||||
|
effect.scheduler = () => {
|
||||||
|
if (instance) job.id = instance.uid
|
||||||
|
queueJob(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
effect.run()
|
||||||
|
}
|
|
@ -1,116 +0,0 @@
|
||||||
import {
|
|
||||||
type BaseWatchErrorCodes,
|
|
||||||
type BaseWatchMiddleware,
|
|
||||||
type BaseWatchOptions,
|
|
||||||
baseWatch,
|
|
||||||
} from '@vue/reactivity'
|
|
||||||
import { NOOP, extend, invokeArrayFns, remove } from '@vue/shared'
|
|
||||||
import {
|
|
||||||
type ComponentInternalInstance,
|
|
||||||
getCurrentInstance,
|
|
||||||
setCurrentInstance,
|
|
||||||
} from './component'
|
|
||||||
import {
|
|
||||||
createVaporRenderingScheduler,
|
|
||||||
queuePostRenderEffect,
|
|
||||||
} from './scheduler'
|
|
||||||
import { handleError as handleErrorWithInstance } from './errorHandling'
|
|
||||||
import { warn } from './warning'
|
|
||||||
import { invokeDirectiveHook } from './directives'
|
|
||||||
|
|
||||||
interface RenderWatchOptions {
|
|
||||||
immediate?: boolean
|
|
||||||
deep?: boolean
|
|
||||||
once?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type WatchStopHandle = () => void
|
|
||||||
|
|
||||||
export function renderEffect(effect: () => void): WatchStopHandle {
|
|
||||||
return doWatch(effect)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function renderWatch(
|
|
||||||
source: any,
|
|
||||||
cb: (value: any, oldValue: any) => void,
|
|
||||||
options?: RenderWatchOptions,
|
|
||||||
): WatchStopHandle {
|
|
||||||
return doWatch(source as any, cb, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
function doWatch(
|
|
||||||
source: any,
|
|
||||||
cb?: any,
|
|
||||||
options?: RenderWatchOptions,
|
|
||||||
): WatchStopHandle {
|
|
||||||
const extendOptions: BaseWatchOptions =
|
|
||||||
cb && options ? extend({}, options) : {}
|
|
||||||
|
|
||||||
if (__DEV__) extendOptions.onWarn = warn
|
|
||||||
|
|
||||||
// TODO: SSR
|
|
||||||
// if (__SSR__) {}
|
|
||||||
|
|
||||||
const instance = getCurrentInstance()
|
|
||||||
extend(extendOptions, {
|
|
||||||
onError: (err: unknown, type: BaseWatchErrorCodes) =>
|
|
||||||
handleErrorWithInstance(err, instance, type),
|
|
||||||
scheduler: createVaporRenderingScheduler(instance),
|
|
||||||
middleware: createMiddleware(instance),
|
|
||||||
})
|
|
||||||
let effect = baseWatch(source, cb, extendOptions)
|
|
||||||
|
|
||||||
const unwatch = !effect
|
|
||||||
? NOOP
|
|
||||||
: () => {
|
|
||||||
effect!.stop()
|
|
||||||
if (instance && instance.scope) {
|
|
||||||
remove(instance.scope.effects!, effect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return unwatch
|
|
||||||
}
|
|
||||||
|
|
||||||
const createMiddleware =
|
|
||||||
(instance: ComponentInternalInstance | null): BaseWatchMiddleware =>
|
|
||||||
next => {
|
|
||||||
let value: unknown
|
|
||||||
// with lifecycle
|
|
||||||
if (instance && instance.isMounted) {
|
|
||||||
const { bu, u, dirs } = instance
|
|
||||||
// beforeUpdate hook
|
|
||||||
const isFirstEffect = !instance.isUpdating
|
|
||||||
if (isFirstEffect) {
|
|
||||||
if (bu) {
|
|
||||||
invokeArrayFns(bu)
|
|
||||||
}
|
|
||||||
if (dirs) {
|
|
||||||
invokeDirectiveHook(instance, 'beforeUpdate')
|
|
||||||
}
|
|
||||||
instance.isUpdating = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const reset = setCurrentInstance(instance)
|
|
||||||
// run callback
|
|
||||||
value = next()
|
|
||||||
reset()
|
|
||||||
|
|
||||||
if (isFirstEffect) {
|
|
||||||
queuePostRenderEffect(() => {
|
|
||||||
instance.isUpdating = false
|
|
||||||
if (dirs) {
|
|
||||||
invokeDirectiveHook(instance, 'updated')
|
|
||||||
}
|
|
||||||
// updated hook
|
|
||||||
if (u) {
|
|
||||||
queuePostRenderEffect(u)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// is not mounted
|
|
||||||
value = next()
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
|
@ -32,7 +32,7 @@ let postFlushIndex = 0
|
||||||
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
|
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
|
||||||
let currentFlushPromise: Promise<void> | null = null
|
let currentFlushPromise: Promise<void> | null = null
|
||||||
|
|
||||||
function queueJob(job: SchedulerJob) {
|
export function queueJob(job: SchedulerJob) {
|
||||||
if (!(job.flags! & SchedulerJobFlags.QUEUED)) {
|
if (!(job.flags! & SchedulerJobFlags.QUEUED)) {
|
||||||
if (job.id == null) {
|
if (job.id == null) {
|
||||||
queue.push(job)
|
queue.push(job)
|
||||||
|
|
Loading…
Reference in New Issue