mirror of https://github.com/vuejs/core.git
feat(runtime-vapor): lifecycle beforeUpdate and updated hooks (#89)
This commit is contained in:
parent
bb8cc447eb
commit
2cce436aaf
|
@ -175,4 +175,82 @@ describe('baseWatch', () => {
|
|||
scope.stop()
|
||||
expect(calls).toEqual(['sync 2', 'post 2'])
|
||||
})
|
||||
test('baseWatch with middleware', async () => {
|
||||
let effectCalls: string[] = []
|
||||
let watchCalls: string[] = []
|
||||
const source = ref(0)
|
||||
|
||||
// effect
|
||||
baseWatch(
|
||||
() => {
|
||||
source.value
|
||||
effectCalls.push('effect')
|
||||
onEffectCleanup(() => effectCalls.push('effect cleanup'))
|
||||
},
|
||||
null,
|
||||
{
|
||||
scheduler,
|
||||
middleware: next => {
|
||||
effectCalls.push('before effect running')
|
||||
next()
|
||||
effectCalls.push('effect ran')
|
||||
},
|
||||
},
|
||||
)
|
||||
// watch
|
||||
baseWatch(
|
||||
() => source.value,
|
||||
() => {
|
||||
watchCalls.push('watch')
|
||||
onEffectCleanup(() => watchCalls.push('watch cleanup'))
|
||||
},
|
||||
{
|
||||
scheduler,
|
||||
middleware: next => {
|
||||
watchCalls.push('before watch running')
|
||||
next()
|
||||
watchCalls.push('watch ran')
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
expect(effectCalls).toEqual([])
|
||||
expect(watchCalls).toEqual([])
|
||||
await nextTick()
|
||||
expect(effectCalls).toEqual([
|
||||
'before effect running',
|
||||
'effect',
|
||||
'effect ran',
|
||||
])
|
||||
expect(watchCalls).toEqual([])
|
||||
effectCalls.length = 0
|
||||
watchCalls.length = 0
|
||||
|
||||
source.value++
|
||||
await nextTick()
|
||||
expect(effectCalls).toEqual([
|
||||
'before effect running',
|
||||
'effect cleanup',
|
||||
'effect',
|
||||
'effect ran',
|
||||
])
|
||||
expect(watchCalls).toEqual(['before watch running', 'watch', 'watch ran'])
|
||||
effectCalls.length = 0
|
||||
watchCalls.length = 0
|
||||
|
||||
source.value++
|
||||
await nextTick()
|
||||
expect(effectCalls).toEqual([
|
||||
'before effect running',
|
||||
'effect cleanup',
|
||||
'effect',
|
||||
'effect ran',
|
||||
])
|
||||
expect(watchCalls).toEqual([
|
||||
'before watch running',
|
||||
'watch cleanup',
|
||||
'watch',
|
||||
'watch ran',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
@ -71,6 +71,7 @@ export interface BaseWatchOptions<Immediate = boolean> extends DebuggerOptions {
|
|||
deep?: boolean
|
||||
once?: boolean
|
||||
scheduler?: Scheduler
|
||||
middleware?: BaseWatchMiddleware
|
||||
onError?: HandleError
|
||||
onWarn?: HandleWarn
|
||||
}
|
||||
|
@ -83,6 +84,7 @@ export type Scheduler = (
|
|||
effect: ReactiveEffect,
|
||||
isInit: boolean,
|
||||
) => void
|
||||
export type BaseWatchMiddleware = (next: () => unknown) => any
|
||||
export type HandleError = (err: unknown, type: BaseWatchErrorCodes) => void
|
||||
export type HandleWarn = (msg: string, ...args: any[]) => void
|
||||
|
||||
|
@ -132,6 +134,7 @@ export function baseWatch(
|
|||
scheduler = DEFAULT_SCHEDULER,
|
||||
onWarn = __DEV__ ? warn : NOOP,
|
||||
onError = DEFAULT_HANDLE_ERROR,
|
||||
middleware,
|
||||
onTrack,
|
||||
onTrigger,
|
||||
}: BaseWatchOptions = EMPTY_OBJ,
|
||||
|
@ -211,6 +214,10 @@ export function baseWatch(
|
|||
activeEffect = currentEffect
|
||||
}
|
||||
}
|
||||
if (middleware) {
|
||||
const baseGetter = getter
|
||||
getter = () => middleware(baseGetter)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
getter = NOOP
|
||||
|
@ -264,31 +271,38 @@ export function baseWatch(
|
|||
? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))
|
||||
: hasChanged(newValue, oldValue))
|
||||
) {
|
||||
// cleanup before running cb again
|
||||
if (cleanup) {
|
||||
cleanup()
|
||||
const next = () => {
|
||||
// cleanup before running cb again
|
||||
if (cleanup) {
|
||||
cleanup()
|
||||
}
|
||||
const currentEffect = activeEffect
|
||||
activeEffect = effect
|
||||
try {
|
||||
callWithAsyncErrorHandling(
|
||||
cb!,
|
||||
onError,
|
||||
BaseWatchErrorCodes.WATCH_CALLBACK,
|
||||
[
|
||||
newValue,
|
||||
// pass undefined as the old value when it's changed for the first time
|
||||
oldValue === INITIAL_WATCHER_VALUE
|
||||
? undefined
|
||||
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
|
||||
? []
|
||||
: oldValue,
|
||||
onEffectCleanup,
|
||||
],
|
||||
)
|
||||
oldValue = newValue
|
||||
} finally {
|
||||
activeEffect = currentEffect
|
||||
}
|
||||
}
|
||||
const currentEffect = activeEffect
|
||||
activeEffect = effect
|
||||
try {
|
||||
callWithAsyncErrorHandling(
|
||||
cb,
|
||||
onError,
|
||||
BaseWatchErrorCodes.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
|
||||
if (middleware) {
|
||||
middleware(next)
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -76,5 +76,6 @@ export {
|
|||
traverse,
|
||||
BaseWatchErrorCodes,
|
||||
type BaseWatchOptions,
|
||||
type BaseWatchMiddleware,
|
||||
type Scheduler,
|
||||
} from './baseWatch'
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { defineComponent } from 'vue'
|
||||
import {
|
||||
nextTick,
|
||||
onBeforeUpdate,
|
||||
onEffectCleanup,
|
||||
onUpdated,
|
||||
ref,
|
||||
render,
|
||||
renderEffect,
|
||||
|
@ -25,6 +27,27 @@ beforeEach(() => {
|
|||
afterEach(() => {
|
||||
host.remove()
|
||||
})
|
||||
const createDemo = (
|
||||
setupFn: (porps: any, ctx: any) => any,
|
||||
renderFn: (ctx: any) => any,
|
||||
) => {
|
||||
const demo = defineComponent({
|
||||
setup(...args) {
|
||||
const returned = setupFn(...args)
|
||||
Object.defineProperty(returned, '__isScriptSetup', {
|
||||
enumerable: false,
|
||||
value: true,
|
||||
})
|
||||
return returned
|
||||
},
|
||||
})
|
||||
demo.render = (ctx: any) => {
|
||||
const t0 = template('<div></div>')
|
||||
renderFn(ctx)
|
||||
return t0()
|
||||
}
|
||||
return () => render(demo as any, {}, '#host')
|
||||
}
|
||||
|
||||
describe('renderWatch', () => {
|
||||
test('effect', async () => {
|
||||
|
@ -53,16 +76,26 @@ describe('renderWatch', () => {
|
|||
expect(dummy).toBe(1)
|
||||
})
|
||||
|
||||
test('scheduling order', async () => {
|
||||
test('should run with the scheduling order', async () => {
|
||||
const calls: string[] = []
|
||||
|
||||
const demo = defineComponent({
|
||||
setup() {
|
||||
const mount = createDemo(
|
||||
() => {
|
||||
// setup
|
||||
const source = ref(0)
|
||||
const renderSource = ref(0)
|
||||
const change = () => source.value++
|
||||
const changeRender = () => renderSource.value++
|
||||
|
||||
// Life Cycle Hooks
|
||||
onUpdated(() => {
|
||||
calls.push(`updated ${source.value}`)
|
||||
})
|
||||
onBeforeUpdate(() => {
|
||||
calls.push(`beforeUpdate ${source.value}`)
|
||||
})
|
||||
|
||||
// Watch API
|
||||
watchPostEffect(() => {
|
||||
const current = source.value
|
||||
calls.push(`post ${current}`)
|
||||
|
@ -78,33 +111,28 @@ describe('renderWatch', () => {
|
|||
calls.push(`sync ${current}`)
|
||||
onEffectCleanup(() => calls.push(`sync cleanup ${current}`))
|
||||
})
|
||||
const __returned__ = { source, change, renderSource, changeRender }
|
||||
Object.defineProperty(__returned__, '__isScriptSetup', {
|
||||
enumerable: false,
|
||||
value: true,
|
||||
})
|
||||
return __returned__
|
||||
return { source, change, renderSource, changeRender }
|
||||
},
|
||||
})
|
||||
// render
|
||||
(_ctx) => {
|
||||
// Render Watch API
|
||||
renderEffect(() => {
|
||||
const current = _ctx.renderSource
|
||||
calls.push(`renderEffect ${current}`)
|
||||
onEffectCleanup(() => calls.push(`renderEffect cleanup ${current}`))
|
||||
})
|
||||
renderWatch(
|
||||
() => _ctx.renderSource,
|
||||
(value) => {
|
||||
calls.push(`renderWatch ${value}`)
|
||||
onEffectCleanup(() => calls.push(`renderWatch cleanup ${value}`))
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
demo.render = (_ctx: any) => {
|
||||
const t0 = template('<div></div>')
|
||||
renderEffect(() => {
|
||||
const current = _ctx.renderSource
|
||||
calls.push(`renderEffect ${current}`)
|
||||
onEffectCleanup(() => calls.push(`renderEffect cleanup ${current}`))
|
||||
})
|
||||
renderWatch(
|
||||
() => _ctx.renderSource,
|
||||
(value) => {
|
||||
calls.push(`renderWatch ${value}`)
|
||||
onEffectCleanup(() => calls.push(`renderWatch cleanup ${value}`))
|
||||
},
|
||||
)
|
||||
return t0()
|
||||
}
|
||||
|
||||
const instance = render(demo as any, {}, '#host')
|
||||
// Mount
|
||||
const instance = mount()
|
||||
const { change, changeRender } = instance.setupState as any
|
||||
|
||||
expect(calls).toEqual(['pre 0', 'sync 0', 'renderEffect 0'])
|
||||
|
@ -114,8 +142,10 @@ describe('renderWatch', () => {
|
|||
expect(calls).toEqual(['post 0'])
|
||||
calls.length = 0
|
||||
|
||||
// Update
|
||||
changeRender()
|
||||
change()
|
||||
|
||||
expect(calls).toEqual(['sync cleanup 0', 'sync 1'])
|
||||
calls.length = 0
|
||||
|
||||
|
@ -123,11 +153,75 @@ describe('renderWatch', () => {
|
|||
expect(calls).toEqual([
|
||||
'pre cleanup 0',
|
||||
'pre 1',
|
||||
'beforeUpdate 1',
|
||||
'renderEffect cleanup 0',
|
||||
'renderEffect 1',
|
||||
'renderWatch 1',
|
||||
'post cleanup 0',
|
||||
'post 1',
|
||||
'updated 1',
|
||||
])
|
||||
})
|
||||
|
||||
test('errors should include the execution location with beforeUpdate hook', async () => {
|
||||
const mount = createDemo(
|
||||
// setup
|
||||
() => {
|
||||
const source = ref()
|
||||
const update = () => source.value++
|
||||
onBeforeUpdate(() => {
|
||||
throw 'error in beforeUpdate'
|
||||
})
|
||||
return { source, update }
|
||||
},
|
||||
// render
|
||||
(ctx) => {
|
||||
renderEffect(() => {
|
||||
ctx.source
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
const instance = mount()
|
||||
const { update } = instance.setupState as any
|
||||
await expect(async () => {
|
||||
update()
|
||||
await nextTick()
|
||||
}).rejects.toThrow('error in beforeUpdate')
|
||||
|
||||
expect(
|
||||
'[Vue warn] Unhandled error during execution of beforeUpdate hook',
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('errors should include the execution location with updated hook', async () => {
|
||||
const mount = createDemo(
|
||||
// setup
|
||||
() => {
|
||||
const source = ref(0)
|
||||
const update = () => source.value++
|
||||
onUpdated(() => {
|
||||
throw 'error in updated'
|
||||
})
|
||||
return { source, update }
|
||||
},
|
||||
// render
|
||||
(ctx) => {
|
||||
renderEffect(() => {
|
||||
ctx.source
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
const instance = mount()
|
||||
const { update } = instance.setupState as any
|
||||
await expect(async () => {
|
||||
update()
|
||||
await nextTick()
|
||||
}).rejects.toThrow('error in updated')
|
||||
|
||||
expect(
|
||||
'[Vue warn] Unhandled error during execution of updated',
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { EffectScope, type Ref, ref } from '@vue/reactivity'
|
||||
import {
|
||||
EffectScope,
|
||||
type Ref,
|
||||
pauseTracking,
|
||||
ref,
|
||||
resetTracking,
|
||||
} from '@vue/reactivity'
|
||||
|
||||
import { EMPTY_OBJ } from '@vue/shared'
|
||||
import type { Block } from './render'
|
||||
|
@ -47,6 +53,7 @@ export interface ComponentInternalInstance {
|
|||
// lifecycle
|
||||
get isMounted(): boolean
|
||||
get isUnmounted(): boolean
|
||||
isUpdating: boolean
|
||||
isUnmountedRef: Ref<boolean>
|
||||
isMountedRef: Ref<boolean>
|
||||
// TODO: registory of provides, lifecycles, ...
|
||||
|
@ -150,11 +157,18 @@ export const createComponentInstance = (
|
|||
|
||||
// lifecycle
|
||||
get isMounted() {
|
||||
return isMountedRef.value
|
||||
pauseTracking()
|
||||
const value = isMountedRef.value
|
||||
resetTracking()
|
||||
return value
|
||||
},
|
||||
get isUnmounted() {
|
||||
return isUnmountedRef.value
|
||||
pauseTracking()
|
||||
const value = isUnmountedRef.value
|
||||
resetTracking()
|
||||
return value
|
||||
},
|
||||
isUpdating: false,
|
||||
isMountedRef,
|
||||
isUnmountedRef,
|
||||
// TODO: registory of provides, appContext, lifecycles, ...
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { isFunction } from '@vue/shared'
|
||||
import { NOOP, isFunction } from '@vue/shared'
|
||||
import { type ComponentInternalInstance, currentInstance } from './component'
|
||||
import { watchEffect } from './apiWatch'
|
||||
import { pauseTracking, resetTracking } from '@vue/reactivity'
|
||||
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
||||
import { renderWatch } from './renderWatch'
|
||||
|
||||
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
|
||||
|
||||
|
@ -27,7 +29,7 @@ export type DirectiveHookName =
|
|||
| 'created'
|
||||
| 'beforeMount'
|
||||
| 'mounted'
|
||||
// | 'beforeUpdate'
|
||||
| 'beforeUpdate'
|
||||
| 'updated'
|
||||
| 'beforeUnmount'
|
||||
| 'unmounted'
|
||||
|
@ -93,12 +95,12 @@ export function withDirectives<T extends Node>(
|
|||
}
|
||||
bindings.push(binding)
|
||||
|
||||
callDirectiveHook(node, binding, 'created')
|
||||
callDirectiveHook(node, binding, instance, 'created')
|
||||
|
||||
watchEffect(() => {
|
||||
if (!instance.isMountedRef.value) return
|
||||
callDirectiveHook(node, binding, 'updated')
|
||||
})
|
||||
// register source
|
||||
if (source) {
|
||||
renderWatch(source, NOOP)
|
||||
}
|
||||
}
|
||||
|
||||
return node
|
||||
|
@ -114,7 +116,7 @@ export function invokeDirectiveHook(
|
|||
for (const node of nodes) {
|
||||
const directives = instance.dirs.get(node) || []
|
||||
for (const binding of directives) {
|
||||
callDirectiveHook(node, binding, name)
|
||||
callDirectiveHook(node, binding, instance, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -122,6 +124,7 @@ export function invokeDirectiveHook(
|
|||
function callDirectiveHook(
|
||||
node: Node,
|
||||
binding: DirectiveBinding,
|
||||
instance: ComponentInternalInstance | null,
|
||||
name: DirectiveHookName,
|
||||
) {
|
||||
const { dir } = binding
|
||||
|
@ -129,9 +132,14 @@ function callDirectiveHook(
|
|||
if (!hook) return
|
||||
|
||||
const newValue = binding.source ? binding.source() : undefined
|
||||
if (name === 'updated' && binding.value === newValue) return
|
||||
|
||||
binding.oldValue = binding.value
|
||||
binding.value = newValue
|
||||
hook(node, binding)
|
||||
// disable tracking inside all lifecycle hooks
|
||||
// since they can potentially be called inside effects.
|
||||
pauseTracking()
|
||||
callWithAsyncErrorHandling(hook, instance, VaporErrorCodes.DIRECTIVE_HOOK, [
|
||||
node,
|
||||
binding,
|
||||
])
|
||||
resetTracking()
|
||||
if (name !== 'beforeUpdate') binding.oldValue = binding.value
|
||||
}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import {
|
||||
type BaseWatchErrorCodes,
|
||||
type BaseWatchMiddleware,
|
||||
type BaseWatchOptions,
|
||||
baseWatch,
|
||||
getCurrentScope,
|
||||
} from '@vue/reactivity'
|
||||
import { NOOP, remove } from '@vue/shared'
|
||||
import { currentInstance } from './component'
|
||||
import { createVaporRenderingScheduler } from './scheduler'
|
||||
import { NOOP, invokeArrayFns, remove } from '@vue/shared'
|
||||
import { type ComponentInternalInstance, currentInstance } from './component'
|
||||
import {
|
||||
createVaporRenderingScheduler,
|
||||
queuePostRenderEffect,
|
||||
} from './scheduler'
|
||||
import { handleError as handleErrorWithInstance } from './errorHandling'
|
||||
import { warn } from './warning'
|
||||
import { invokeDirectiveHook } from './directive'
|
||||
|
||||
type WatchStopHandle = () => void
|
||||
|
||||
|
@ -28,8 +33,6 @@ function doWatch(source: any, cb?: any): WatchStopHandle {
|
|||
|
||||
if (__DEV__) extendOptions.onWarn = warn
|
||||
|
||||
// TODO: Life Cycle Hooks
|
||||
|
||||
// TODO: SSR
|
||||
// if (__SSR__) {}
|
||||
|
||||
|
@ -40,6 +43,8 @@ function doWatch(source: any, cb?: any): WatchStopHandle {
|
|||
handleErrorWithInstance(err, instance, type)
|
||||
extendOptions.scheduler = createVaporRenderingScheduler(instance)
|
||||
|
||||
extendOptions.middleware = createMiddleware(instance)
|
||||
|
||||
let effect = baseWatch(source, cb, extendOptions)
|
||||
|
||||
const unwatch = !effect
|
||||
|
@ -53,3 +58,44 @@ function doWatch(source: any, cb?: any): WatchStopHandle {
|
|||
|
||||
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
|
||||
}
|
||||
|
||||
// run callback
|
||||
value = next()
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { Scheduler } from '@vue/reactivity'
|
||||
import type { ComponentInternalInstance } from './component'
|
||||
import { isArray } from '@vue/shared'
|
||||
|
||||
export interface SchedulerJob extends Function {
|
||||
id?: number
|
||||
|
@ -73,15 +74,22 @@ function queueJob(job: SchedulerJob) {
|
|||
}
|
||||
}
|
||||
|
||||
export function queuePostRenderEffect(cb: SchedulerJob) {
|
||||
if (
|
||||
!activePostFlushCbs ||
|
||||
!activePostFlushCbs.includes(
|
||||
cb,
|
||||
cb.allowRecurse ? postFlushIndex + 1 : postFlushIndex,
|
||||
)
|
||||
) {
|
||||
pendingPostFlushCbs.push(cb)
|
||||
export function queuePostRenderEffect(cb: SchedulerJobs) {
|
||||
if (!isArray(cb)) {
|
||||
if (
|
||||
!activePostFlushCbs ||
|
||||
!activePostFlushCbs.includes(
|
||||
cb,
|
||||
cb.allowRecurse ? postFlushIndex + 1 : postFlushIndex,
|
||||
)
|
||||
) {
|
||||
pendingPostFlushCbs.push(cb)
|
||||
}
|
||||
} else {
|
||||
// if cb is an array, it is a component lifecycle hook which can only be
|
||||
// triggered by a job, which is already deduped in the main queue, so
|
||||
// we can skip duplicate check here to improve perf
|
||||
pendingPostFlushCbs.push(...cb)
|
||||
}
|
||||
queueFlush()
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import {
|
|||
onMounted,
|
||||
onBeforeMount,
|
||||
getCurrentInstance,
|
||||
onBeforeUpdate,
|
||||
onUpdated,
|
||||
} from 'vue/vapor'
|
||||
|
||||
const instance = getCurrentInstance()!
|
||||
|
@ -26,12 +28,24 @@ onMounted(() => {
|
|||
count.value++
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
onBeforeUpdate(() => {
|
||||
console.log('before updated')
|
||||
})
|
||||
onUpdated(() => {
|
||||
console.log('updated')
|
||||
})
|
||||
|
||||
const log = (arg: any) => {
|
||||
console.log('callback in render effect')
|
||||
return arg
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="red">Counter</h1>
|
||||
<div>The number is {{ count }}.</div>
|
||||
<div>The number is {{ log(count) }}.</div>
|
||||
<div>{{ count }} * 2 = {{ double }}</div>
|
||||
<div style="display: flex; gap: 8px">
|
||||
<button @click="inc">inc</button>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { ObjectDirective, FunctionDirective, ref } from '@vue/vapor'
|
||||
|
||||
const text = ref('created (overwrite by v-text), ')
|
||||
const counter = ref(0)
|
||||
const vDirective: ObjectDirective<HTMLDivElement, undefined> = {
|
||||
created(node) {
|
||||
if (!node.parentElement) {
|
||||
|
@ -17,9 +18,15 @@ const vDirective: ObjectDirective<HTMLDivElement, undefined> = {
|
|||
mounted(node) {
|
||||
if (node.parentElement) node.textContent += 'mounted, '
|
||||
},
|
||||
beforeUpdate(node, binding) {
|
||||
console.log('beforeUpdate', binding, node)
|
||||
},
|
||||
updated(node, binding) {
|
||||
console.log('updated', binding, node)
|
||||
},
|
||||
}
|
||||
const vDirectiveSimple: FunctionDirective<HTMLDivElement> = (node, binding) => {
|
||||
console.log(node, binding)
|
||||
console.log('v-directive-simple:', node, binding)
|
||||
}
|
||||
const handleClick = () => {
|
||||
text.value = 'change'
|
||||
|
@ -33,4 +40,15 @@ const handleClick = () => {
|
|||
v-directive-simple="text"
|
||||
@click="handleClick"
|
||||
/>
|
||||
<button @click="counter++">
|
||||
{{ counter }} (Click to Update Other Element)
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
html {
|
||||
color-scheme: dark;
|
||||
background-color: #000;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue