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 {
|
||||
nextTick,
|
||||
onBeforeUpdate,
|
||||
onUpdated,
|
||||
onWatcherCleanup,
|
||||
ref,
|
||||
renderEffect,
|
||||
renderWatch,
|
||||
template,
|
||||
watchEffect,
|
||||
watchPostEffect,
|
||||
|
@ -31,8 +30,8 @@ const createDemo = (setupFn: () => any, renderFn: (ctx: any) => any) =>
|
|||
},
|
||||
})
|
||||
|
||||
describe('renderWatch', () => {
|
||||
test('effect', async () => {
|
||||
describe('renderEffect', () => {
|
||||
test('basic', async () => {
|
||||
let dummy: any
|
||||
const source = ref(0)
|
||||
renderEffect(() => {
|
||||
|
@ -58,26 +57,6 @@ describe('renderWatch', () => {
|
|||
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 () => {
|
||||
const calls: string[] = []
|
||||
|
||||
|
@ -101,17 +80,17 @@ describe('renderWatch', () => {
|
|||
watchPostEffect(() => {
|
||||
const current = source.value
|
||||
calls.push(`post ${current}`)
|
||||
onWatcherCleanup(() => calls.push(`post cleanup ${current}`))
|
||||
onEffectCleanup(() => calls.push(`post cleanup ${current}`))
|
||||
})
|
||||
watchEffect(() => {
|
||||
const current = source.value
|
||||
calls.push(`pre ${current}`)
|
||||
onWatcherCleanup(() => calls.push(`pre cleanup ${current}`))
|
||||
onEffectCleanup(() => calls.push(`pre cleanup ${current}`))
|
||||
})
|
||||
watchSyncEffect(() => {
|
||||
const current = source.value
|
||||
calls.push(`sync ${current}`)
|
||||
onWatcherCleanup(() => calls.push(`sync cleanup ${current}`))
|
||||
onEffectCleanup(() => calls.push(`sync cleanup ${current}`))
|
||||
})
|
||||
return { source, change, renderSource, changeRender }
|
||||
},
|
||||
|
@ -121,15 +100,8 @@ describe('renderWatch', () => {
|
|||
renderEffect(() => {
|
||||
const current = _ctx.renderSource
|
||||
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()
|
||||
const { change, changeRender } = instance.setupState as any
|
||||
|
@ -151,7 +123,6 @@ describe('renderWatch', () => {
|
|||
'beforeUpdate 1',
|
||||
'renderEffect cleanup 0',
|
||||
'renderEffect 1',
|
||||
'renderWatch 1',
|
||||
'post cleanup 0',
|
||||
'post 1',
|
||||
'updated 1',
|
||||
|
@ -172,8 +143,6 @@ describe('renderWatch', () => {
|
|||
'beforeUpdate 2',
|
||||
'renderEffect cleanup 1',
|
||||
'renderEffect 2',
|
||||
'renderWatch cleanup 1',
|
||||
'renderWatch 2',
|
||||
'post cleanup 1',
|
||||
'post 2',
|
||||
'updated 2',
|
|
@ -1,7 +1,7 @@
|
|||
import { type EffectScope, effectScope, isReactive } from '@vue/reactivity'
|
||||
import { isArray, isObject, isString } from '@vue/shared'
|
||||
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 { 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 EffectScope, effectScope } from '@vue/reactivity'
|
||||
import { createComment, createTextNode, insert, remove } from './dom/element'
|
||||
|
@ -12,6 +12,8 @@ export const createIf = (
|
|||
b2?: BlockFn,
|
||||
// hydrationNode?: Node,
|
||||
): Fragment => {
|
||||
let newValue: any
|
||||
let oldValue: any
|
||||
let branch: BlockFn | undefined
|
||||
let parent: ParentNode | undefined | null
|
||||
let block: Block | undefined
|
||||
|
@ -29,15 +31,14 @@ export const createIf = (
|
|||
// setCurrentHydrationNode(hydrationNode!)
|
||||
// }
|
||||
|
||||
renderWatch(
|
||||
() => !!condition(),
|
||||
value => {
|
||||
renderEffect(() => {
|
||||
if ((newValue = !!condition()) !== oldValue) {
|
||||
parent ||= anchor.parentNode
|
||||
if (block) {
|
||||
scope!.stop()
|
||||
remove(block, parent!)
|
||||
}
|
||||
if ((branch = value ? b1 : b2)) {
|
||||
if ((branch = (oldValue = newValue) ? b1 : b2)) {
|
||||
scope = effectScope()
|
||||
fragment.nodes = block = scope.run(branch)!
|
||||
parent && insert(block, parent, anchor)
|
||||
|
@ -45,9 +46,8 @@ export const createIf = (
|
|||
scope = block = undefined
|
||||
fragment.nodes = []
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: SSR
|
||||
// if (isHydrating) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { NOOP, isFunction } from '@vue/shared'
|
||||
import { isFunction } from '@vue/shared'
|
||||
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 { renderWatch } from './renderWatch'
|
||||
import { renderEffect } from './renderEffect'
|
||||
|
||||
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
|
||||
|
||||
|
@ -100,8 +100,12 @@ export function withDirectives<T extends Node>(
|
|||
|
||||
// register source
|
||||
if (source) {
|
||||
// callback will be overridden by middleware
|
||||
renderWatch(source, NOOP, { deep: dir.deep })
|
||||
if (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 SetupFn,
|
||||
} from './component'
|
||||
export { renderEffect, renderWatch } from './renderWatch'
|
||||
export { renderEffect } from './renderEffect'
|
||||
export {
|
||||
watch,
|
||||
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>
|
||||
let currentFlushPromise: Promise<void> | null = null
|
||||
|
||||
function queueJob(job: SchedulerJob) {
|
||||
export function queueJob(job: SchedulerJob) {
|
||||
if (!(job.flags! & SchedulerJobFlags.QUEUED)) {
|
||||
if (job.id == null) {
|
||||
queue.push(job)
|
||||
|
|
Loading…
Reference in New Issue