mirror of https://github.com/vuejs/core.git
refactor!: drop custom directives (#274)
This commit is contained in:
parent
6791c887da
commit
e1bedb8ad0
|
@ -19,6 +19,7 @@
|
||||||
"paths": {
|
"paths": {
|
||||||
"vue": ["../packages/vue/src"],
|
"vue": ["../packages/vue/src"],
|
||||||
"@vue/vapor": ["../packages/vue-vapor/src"],
|
"@vue/vapor": ["../packages/vue-vapor/src"],
|
||||||
|
"vue/vapor": ["../packages/vue-vapor/src"],
|
||||||
"@vue/*": ["../packages/*/src"]
|
"@vue/*": ["../packages/*/src"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -153,7 +153,7 @@ describe('api: createVaporApp', () => {
|
||||||
expect(host.innerHTML).toBe(`foobar!barbaz!`)
|
expect(host.innerHTML).toBe(`foobar!barbaz!`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('directive', () => {
|
test.todo('directive', () => {
|
||||||
const spy1 = vi.fn()
|
const spy1 = vi.fn()
|
||||||
const spy2 = vi.fn()
|
const spy2 = vi.fn()
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
|
||||||
describe('directives', () => {
|
describe.todo('directives', () => {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { makeRender } from '../_utils'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
|
||||||
describe('directives', () => {
|
describe.todo('directives', () => {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ const setDOMProps = (el: any, props: Array<[key: string, value: any]>) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('directive: v-model', () => {
|
describe.todo('directive: v-model', () => {
|
||||||
test('should work with text input', async () => {
|
test('should work with text input', async () => {
|
||||||
const spy = vi.fn()
|
const spy = vi.fn()
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ const createDemo = (defaultValue: boolean) =>
|
||||||
on(n1 as HTMLElement, 'click', () => handleClick)
|
on(n1 as HTMLElement, 'click', () => handleClick)
|
||||||
return n0
|
return n0
|
||||||
})
|
})
|
||||||
describe('directive: v-show', () => {
|
describe.todo('directive: v-show', () => {
|
||||||
test('basic', async () => {
|
test('basic', async () => {
|
||||||
const { host } = createDemo(true).render()
|
const { host } = createDemo(true).render()
|
||||||
const btn = host.querySelector('button')
|
const btn = host.querySelector('button')
|
||||||
|
|
|
@ -194,7 +194,7 @@ describe('createFor', () => {
|
||||||
expect(host.innerHTML).toBe('<!--for-->')
|
expect(host.innerHTML).toBe('<!--for-->')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should work with directive hooks', async () => {
|
test.fails('should work with directive hooks', async () => {
|
||||||
const calls: string[] = []
|
const calls: string[] = []
|
||||||
const list = ref([0])
|
const list = ref([0])
|
||||||
const update = ref(0)
|
const update = ref(0)
|
||||||
|
|
|
@ -134,7 +134,7 @@ describe('createIf', () => {
|
||||||
expect(host.innerHTML).toBe('<!--if-->')
|
expect(host.innerHTML).toBe('<!--if-->')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should work with directive hooks', async () => {
|
test.todo('should work with directive hooks', async () => {
|
||||||
const calls: string[] = []
|
const calls: string[] = []
|
||||||
const show1 = ref(true)
|
const show1 = ref(true)
|
||||||
const show2 = ref(true)
|
const show2 = ref(true)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {
|
import {
|
||||||
|
type EffectScope,
|
||||||
type ShallowRef,
|
type ShallowRef,
|
||||||
getCurrentScope,
|
effectScope,
|
||||||
isReactive,
|
isReactive,
|
||||||
proxyRefs,
|
proxyRefs,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
traverse,
|
|
||||||
triggerRef,
|
triggerRef,
|
||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
import { isArray, isObject, isString } from '@vue/shared'
|
import { isArray, isObject, isString } from '@vue/shared'
|
||||||
|
@ -18,17 +18,11 @@ import { type Block, type Fragment, fragmentKey } from './apiRender'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { currentInstance } from './component'
|
import { currentInstance } from './component'
|
||||||
import { componentKey } from './component'
|
import { componentKey } from './component'
|
||||||
import { BlockEffectScope, isRenderEffectScope } from './blockEffectScope'
|
|
||||||
import {
|
|
||||||
createChildFragmentDirectives,
|
|
||||||
invokeWithMount,
|
|
||||||
invokeWithUnmount,
|
|
||||||
invokeWithUpdate,
|
|
||||||
} from './directivesChildFragment'
|
|
||||||
import type { DynamicSlot } from './componentSlots'
|
import type { DynamicSlot } from './componentSlots'
|
||||||
|
import { renderEffect } from './renderEffect'
|
||||||
|
|
||||||
interface ForBlock extends Fragment {
|
interface ForBlock extends Fragment {
|
||||||
scope: BlockEffectScope
|
scope: EffectScope
|
||||||
state: [
|
state: [
|
||||||
item: ShallowRef<any>,
|
item: ShallowRef<any>,
|
||||||
key: ShallowRef<any>,
|
key: ShallowRef<any>,
|
||||||
|
@ -53,8 +47,6 @@ export const createFor = (
|
||||||
let oldBlocks: ForBlock[] = []
|
let oldBlocks: ForBlock[] = []
|
||||||
let newBlocks: ForBlock[]
|
let newBlocks: ForBlock[]
|
||||||
let parent: ParentNode | undefined | null
|
let parent: ParentNode | undefined | null
|
||||||
const update = getMemo ? updateWithMemo : updateWithoutMemo
|
|
||||||
const parentScope = getCurrentScope()!
|
|
||||||
const parentAnchor = __DEV__ ? createComment('for') : createTextNode()
|
const parentAnchor = __DEV__ ? createComment('for') : createTextNode()
|
||||||
const ref: Fragment = {
|
const ref: Fragment = {
|
||||||
nodes: oldBlocks,
|
nodes: oldBlocks,
|
||||||
|
@ -62,25 +54,17 @@ export const createFor = (
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = currentInstance!
|
const instance = currentInstance!
|
||||||
if (__DEV__ && (!instance || !isRenderEffectScope(parentScope))) {
|
if (__DEV__ && !instance) {
|
||||||
warn('createFor() can only be used inside setup()')
|
warn('createFor() can only be used inside setup()')
|
||||||
}
|
}
|
||||||
|
|
||||||
createChildFragmentDirectives(
|
const update = getMemo ? updateWithMemo : updateWithoutMemo
|
||||||
parentAnchor,
|
once ? renderList() : renderEffect(renderList)
|
||||||
() => oldBlocks.map(b => b.scope),
|
|
||||||
// source getter
|
|
||||||
() => traverse(src(), 1),
|
|
||||||
// init cb
|
|
||||||
getValue => doFor(getValue()),
|
|
||||||
// effect cb
|
|
||||||
getValue => doFor(getValue()),
|
|
||||||
once,
|
|
||||||
)
|
|
||||||
|
|
||||||
return ref
|
return ref
|
||||||
|
|
||||||
function doFor(source: any) {
|
function renderList() {
|
||||||
|
const source = src()
|
||||||
const newLength = getLength(source)
|
const newLength = getLength(source)
|
||||||
const oldLength = oldBlocks.length
|
const oldLength = oldBlocks.length
|
||||||
newBlocks = new Array(newLength)
|
newBlocks = new Array(newLength)
|
||||||
|
@ -265,7 +249,7 @@ export const createFor = (
|
||||||
idx: number,
|
idx: number,
|
||||||
anchor: Node = parentAnchor,
|
anchor: Node = parentAnchor,
|
||||||
): ForBlock {
|
): ForBlock {
|
||||||
const scope = new BlockEffectScope(instance, parentScope)
|
const scope = effectScope()
|
||||||
|
|
||||||
const [item, key, index] = getItem(source, idx)
|
const [item, key, index] = getItem(source, idx)
|
||||||
const state = [
|
const state = [
|
||||||
|
@ -283,11 +267,9 @@ export const createFor = (
|
||||||
})
|
})
|
||||||
block.nodes = scope.run(() => renderItem(proxyRefs(state)))!
|
block.nodes = scope.run(() => renderItem(proxyRefs(state)))!
|
||||||
|
|
||||||
invokeWithMount(scope, () => {
|
// TODO v-memo
|
||||||
// TODO v-memo
|
// if (getMemo) block.update()
|
||||||
// if (getMemo) block.update()
|
if (parent) insert(block.nodes, parent, anchor)
|
||||||
if (parent) insert(block.nodes, parent, anchor)
|
|
||||||
})
|
|
||||||
|
|
||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
|
@ -326,7 +308,6 @@ export const createFor = (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needsUpdate) setState(block, newItem, newKey, newIndex)
|
if (needsUpdate) setState(block, newItem, newKey, newIndex)
|
||||||
invokeWithUpdate(block.scope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateWithoutMemo(
|
function updateWithoutMemo(
|
||||||
|
@ -344,13 +325,11 @@ export const createFor = (
|
||||||
(!isReactive(newItem) && isObject(newItem))
|
(!isReactive(newItem) && isObject(newItem))
|
||||||
|
|
||||||
if (needsUpdate) setState(block, newItem, newKey, newIndex)
|
if (needsUpdate) setState(block, newItem, newKey, newIndex)
|
||||||
invokeWithUpdate(block.scope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function unmount({ nodes, scope }: ForBlock) {
|
function unmount({ nodes, scope }: ForBlock) {
|
||||||
invokeWithUnmount(scope, () => {
|
removeBlock(nodes, parent!)
|
||||||
removeBlock(nodes, parent!)
|
scope.stop()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,7 @@
|
||||||
|
import { renderEffect } from './renderEffect'
|
||||||
import { type Block, type Fragment, fragmentKey } from './apiRender'
|
import { type Block, type Fragment, fragmentKey } from './apiRender'
|
||||||
import { getCurrentScope } 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'
|
||||||
import { currentInstance } from './component'
|
|
||||||
import { warn } from './warning'
|
|
||||||
import { BlockEffectScope, isRenderEffectScope } from './blockEffectScope'
|
|
||||||
import {
|
|
||||||
createChildFragmentDirectives,
|
|
||||||
invokeWithMount,
|
|
||||||
invokeWithUnmount,
|
|
||||||
invokeWithUpdate,
|
|
||||||
} from './directivesChildFragment'
|
|
||||||
|
|
||||||
type BlockFn = () => Block
|
type BlockFn = () => Block
|
||||||
|
|
||||||
|
@ -26,8 +18,7 @@ export const createIf = (
|
||||||
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
|
||||||
let scope: BlockEffectScope | undefined
|
let scope: EffectScope | undefined
|
||||||
const parentScope = getCurrentScope()!
|
|
||||||
const anchor = __DEV__ ? createComment('if') : createTextNode()
|
const anchor = __DEV__ ? createComment('if') : createTextNode()
|
||||||
const fragment: Fragment = {
|
const fragment: Fragment = {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
|
@ -35,37 +26,17 @@ export const createIf = (
|
||||||
[fragmentKey]: true,
|
[fragmentKey]: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = currentInstance!
|
|
||||||
if (__DEV__ && (!instance || !isRenderEffectScope(parentScope))) {
|
|
||||||
warn('createIf() can only be used inside setup()')
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: SSR
|
// TODO: SSR
|
||||||
// if (isHydrating) {
|
// if (isHydrating) {
|
||||||
// parent = hydrationNode!.parentNode
|
// parent = hydrationNode!.parentNode
|
||||||
// setCurrentHydrationNode(hydrationNode!)
|
// setCurrentHydrationNode(hydrationNode!)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
createChildFragmentDirectives(
|
if (once) {
|
||||||
anchor,
|
doIf()
|
||||||
() => (scope ? [scope] : []),
|
} else {
|
||||||
// source getter
|
renderEffect(() => doIf())
|
||||||
condition,
|
}
|
||||||
// init cb
|
|
||||||
getValue => {
|
|
||||||
newValue = !!getValue()
|
|
||||||
doIf()
|
|
||||||
},
|
|
||||||
// effect cb
|
|
||||||
getValue => {
|
|
||||||
if ((newValue = !!getValue()) !== oldValue) {
|
|
||||||
doIf()
|
|
||||||
} else if (scope) {
|
|
||||||
invokeWithUpdate(scope)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
once,
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: SSR
|
// TODO: SSR
|
||||||
// if (isHydrating) {
|
// if (isHydrating) {
|
||||||
|
@ -75,17 +46,20 @@ export const createIf = (
|
||||||
return fragment
|
return fragment
|
||||||
|
|
||||||
function doIf() {
|
function doIf() {
|
||||||
parent ||= anchor.parentNode
|
if ((newValue = !!condition()) !== oldValue) {
|
||||||
if (block) {
|
parent ||= anchor.parentNode
|
||||||
invokeWithUnmount(scope!, () => remove(block!, parent!))
|
if (block) {
|
||||||
}
|
scope!.stop()
|
||||||
if ((branch = (oldValue = newValue) ? b1 : b2)) {
|
remove(block, parent!)
|
||||||
scope = new BlockEffectScope(instance, parentScope)
|
}
|
||||||
fragment.nodes = block = scope.run(branch)!
|
if ((branch = (oldValue = newValue) ? b1 : b2)) {
|
||||||
invokeWithMount(scope, () => parent && insert(block!, parent, anchor))
|
scope = effectScope()
|
||||||
} else {
|
fragment.nodes = block = scope.run(branch)!
|
||||||
scope = block = undefined
|
parent && insert(block, parent, anchor)
|
||||||
fragment.nodes = []
|
} else {
|
||||||
|
scope = block = undefined
|
||||||
|
fragment.nodes = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { EffectScope } from '@vue/reactivity'
|
|
||||||
import type { ComponentInternalInstance } from './component'
|
|
||||||
import type { DirectiveBindingsMap } from './directives'
|
|
||||||
|
|
||||||
export class BlockEffectScope extends EffectScope {
|
|
||||||
/**
|
|
||||||
* instance
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
it: ComponentInternalInstance
|
|
||||||
/**
|
|
||||||
* isMounted
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
im: boolean
|
|
||||||
/**
|
|
||||||
* directives
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
dirs?: DirectiveBindingsMap
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
instance: ComponentInternalInstance,
|
|
||||||
parentScope: EffectScope | null,
|
|
||||||
) {
|
|
||||||
super(false, parentScope || undefined)
|
|
||||||
this.im = false
|
|
||||||
this.it = instance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isRenderEffectScope(
|
|
||||||
scope: EffectScope | undefined,
|
|
||||||
): scope is BlockEffectScope {
|
|
||||||
return scope instanceof BlockEffectScope
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { isRef } from '@vue/reactivity'
|
import { EffectScope, isRef } from '@vue/reactivity'
|
||||||
import {
|
import {
|
||||||
EMPTY_OBJ,
|
EMPTY_OBJ,
|
||||||
hasOwn,
|
hasOwn,
|
||||||
|
@ -31,7 +31,6 @@ import {
|
||||||
createAppContext,
|
createAppContext,
|
||||||
} from './apiCreateVaporApp'
|
} from './apiCreateVaporApp'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
import type { Data } from '@vue/runtime-shared'
|
||||||
import { BlockEffectScope } from './blockEffectScope'
|
|
||||||
|
|
||||||
export type Component = FunctionalComponent | ObjectComponent
|
export type Component = FunctionalComponent | ObjectComponent
|
||||||
|
|
||||||
|
@ -161,7 +160,7 @@ export interface ComponentInternalInstance {
|
||||||
root: ComponentInternalInstance
|
root: ComponentInternalInstance
|
||||||
|
|
||||||
provides: Data
|
provides: Data
|
||||||
scope: BlockEffectScope
|
scope: EffectScope
|
||||||
comps: Set<ComponentInternalInstance>
|
comps: Set<ComponentInternalInstance>
|
||||||
|
|
||||||
rawProps: NormalizedRawProps
|
rawProps: NormalizedRawProps
|
||||||
|
@ -283,7 +282,7 @@ export function createComponentInstance(
|
||||||
parent,
|
parent,
|
||||||
root: null!, // set later
|
root: null!, // set later
|
||||||
|
|
||||||
scope: null!,
|
scope: new EffectScope(true /* detached */)!,
|
||||||
provides: parent ? parent.provides : Object.create(_appContext.provides),
|
provides: parent ? parent.provides : Object.create(_appContext.provides),
|
||||||
type: component,
|
type: component,
|
||||||
comps: new Set(),
|
comps: new Set(),
|
||||||
|
@ -358,7 +357,6 @@ export function createComponentInstance(
|
||||||
// [VaporLifecycleHooks.SERVER_PREFETCH]: null,
|
// [VaporLifecycleHooks.SERVER_PREFETCH]: null,
|
||||||
}
|
}
|
||||||
instance.root = parent ? parent.root : instance
|
instance.root = parent ? parent.root : instance
|
||||||
instance.scope = new BlockEffectScope(instance, parent && parent.scope)
|
|
||||||
initProps(instance, rawProps, !isFunction(component), once)
|
initProps(instance, rawProps, !isFunction(component), once)
|
||||||
initSlots(instance, slots)
|
initSlots(instance, slots)
|
||||||
instance.emit = emit.bind(null, instance)
|
instance.emit = emit.bind(null, instance)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { invokeArrayFns } from '@vue/shared'
|
||||||
import type { VaporLifecycleHooks } from './enums'
|
import type { VaporLifecycleHooks } from './enums'
|
||||||
import { type ComponentInternalInstance, setCurrentInstance } from './component'
|
import { type ComponentInternalInstance, setCurrentInstance } from './component'
|
||||||
import { queuePostFlushCb } from './scheduler'
|
import { queuePostFlushCb } from './scheduler'
|
||||||
import { type DirectiveHookName, invokeDirectiveHook } from './directives'
|
import type { DirectiveHookName } from './directives'
|
||||||
|
|
||||||
export function invokeLifecycle(
|
export function invokeLifecycle(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
|
@ -24,8 +24,6 @@ export function invokeLifecycle(
|
||||||
}
|
}
|
||||||
post ? queuePostFlushCb(fn) : fn()
|
post ? queuePostFlushCb(fn) : fn()
|
||||||
}
|
}
|
||||||
|
|
||||||
invokeDirectiveHook(instance, directive, instance.scope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function invokeSub() {
|
function invokeSub() {
|
||||||
|
|
|
@ -1,27 +1,6 @@
|
||||||
import { invokeArrayFns, isBuiltInDirective, isFunction } from '@vue/shared'
|
import { isBuiltInDirective } from '@vue/shared'
|
||||||
import {
|
import { type ComponentInternalInstance, currentInstance } from './component'
|
||||||
type ComponentInternalInstance,
|
|
||||||
currentInstance,
|
|
||||||
isVaporComponent,
|
|
||||||
setCurrentInstance,
|
|
||||||
} from './component'
|
|
||||||
import {
|
|
||||||
EffectFlags,
|
|
||||||
ReactiveEffect,
|
|
||||||
getCurrentScope,
|
|
||||||
pauseTracking,
|
|
||||||
resetTracking,
|
|
||||||
traverse,
|
|
||||||
} from '@vue/reactivity'
|
|
||||||
import {
|
|
||||||
VaporErrorCodes,
|
|
||||||
callWithAsyncErrorHandling,
|
|
||||||
callWithErrorHandling,
|
|
||||||
} from './errorHandling'
|
|
||||||
import { type SchedulerJob, queueJob, queuePostFlushCb } from './scheduler'
|
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { type BlockEffectScope, isRenderEffectScope } from './blockEffectScope'
|
|
||||||
import { normalizeBlock } from './dom/element'
|
|
||||||
|
|
||||||
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
|
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
|
||||||
|
|
||||||
|
@ -98,168 +77,7 @@ export function withDirectives<T extends ComponentInternalInstance | Node>(
|
||||||
return nodeOrComponent
|
return nodeOrComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
let node: Node
|
// NOOP
|
||||||
if (isVaporComponent(nodeOrComponent)) {
|
|
||||||
const root = getComponentNode(nodeOrComponent)
|
|
||||||
if (!root) return nodeOrComponent
|
|
||||||
node = root
|
|
||||||
} else {
|
|
||||||
node = nodeOrComponent
|
|
||||||
}
|
|
||||||
|
|
||||||
let bindings: DirectiveBinding[]
|
|
||||||
const instance = currentInstance!
|
|
||||||
const parentScope = getCurrentScope() as BlockEffectScope
|
|
||||||
|
|
||||||
if (__DEV__ && !isRenderEffectScope(parentScope)) {
|
|
||||||
warn(`Directives should be used inside of RenderEffectScope.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const directivesMap = (parentScope.dirs ||= new Map())
|
|
||||||
if (!(bindings = directivesMap.get(node))) {
|
|
||||||
directivesMap.set(node, (bindings = []))
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const directive of directives) {
|
|
||||||
let [dir, source, arg, modifiers] = directive
|
|
||||||
if (!dir) continue
|
|
||||||
if (isFunction(dir)) {
|
|
||||||
dir = {
|
|
||||||
mounted: dir,
|
|
||||||
updated: dir,
|
|
||||||
} satisfies ObjectDirective
|
|
||||||
}
|
|
||||||
|
|
||||||
const binding: DirectiveBinding = {
|
|
||||||
dir,
|
|
||||||
instance,
|
|
||||||
value: null, // set later
|
|
||||||
oldValue: undefined,
|
|
||||||
arg,
|
|
||||||
modifiers,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source) {
|
|
||||||
if (dir.deep) {
|
|
||||||
const deep = dir.deep === true ? undefined : dir.deep
|
|
||||||
const baseSource = source
|
|
||||||
source = () => traverse(baseSource(), deep)
|
|
||||||
}
|
|
||||||
|
|
||||||
const effect = new ReactiveEffect(() =>
|
|
||||||
callWithErrorHandling(
|
|
||||||
source!,
|
|
||||||
instance,
|
|
||||||
VaporErrorCodes.RENDER_FUNCTION,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
const triggerRenderingUpdate = createRenderingUpdateTrigger(
|
|
||||||
instance,
|
|
||||||
effect,
|
|
||||||
)
|
|
||||||
effect.scheduler = () => queueJob(triggerRenderingUpdate)
|
|
||||||
|
|
||||||
binding.source = effect.run.bind(effect)
|
|
||||||
}
|
|
||||||
|
|
||||||
bindings.push(binding)
|
|
||||||
|
|
||||||
callDirectiveHook(node, binding, instance, 'created')
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodeOrComponent
|
return nodeOrComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
function getComponentNode(component: ComponentInternalInstance) {
|
|
||||||
if (!component.block) return
|
|
||||||
|
|
||||||
const nodes = normalizeBlock(component.block)
|
|
||||||
if (nodes.length !== 1) {
|
|
||||||
warn(
|
|
||||||
`Runtime directive used on component with non-element root node. ` +
|
|
||||||
`The directives will not function as intended.`,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodes[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function invokeDirectiveHook(
|
|
||||||
instance: ComponentInternalInstance | null,
|
|
||||||
name: DirectiveHookName,
|
|
||||||
scope: BlockEffectScope,
|
|
||||||
): void {
|
|
||||||
const { dirs } = scope
|
|
||||||
if (name === 'mounted') scope.im = true
|
|
||||||
if (!dirs) return
|
|
||||||
const iterator = dirs.entries()
|
|
||||||
for (const [node, bindings] of iterator) {
|
|
||||||
for (const binding of bindings) {
|
|
||||||
callDirectiveHook(node, binding, instance, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function callDirectiveHook(
|
|
||||||
node: Node,
|
|
||||||
binding: DirectiveBinding,
|
|
||||||
instance: ComponentInternalInstance | null,
|
|
||||||
name: DirectiveHookName,
|
|
||||||
) {
|
|
||||||
if (name === 'beforeUpdate') binding.oldValue = binding.value
|
|
||||||
const { dir } = binding
|
|
||||||
const hook = dir[name]
|
|
||||||
if (!hook) return
|
|
||||||
|
|
||||||
const newValue = binding.source ? binding.source() : undefined
|
|
||||||
binding.value = newValue
|
|
||||||
// disable tracking inside all lifecycle hooks
|
|
||||||
// since they can potentially be called inside effects.
|
|
||||||
pauseTracking()
|
|
||||||
callWithAsyncErrorHandling(hook, instance, VaporErrorCodes.DIRECTIVE_HOOK, [
|
|
||||||
node,
|
|
||||||
binding,
|
|
||||||
])
|
|
||||||
resetTracking()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createRenderingUpdateTrigger(
|
|
||||||
instance: ComponentInternalInstance,
|
|
||||||
effect: ReactiveEffect,
|
|
||||||
): SchedulerJob {
|
|
||||||
job.id = instance.uid
|
|
||||||
return job
|
|
||||||
function job() {
|
|
||||||
if (!(effect.flags & EffectFlags.ACTIVE) || !effect.dirty) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (instance.isMounted && !instance.isUpdating) {
|
|
||||||
instance.isUpdating = true
|
|
||||||
const reset = setCurrentInstance(instance)
|
|
||||||
|
|
||||||
const { bu, u, scope } = instance
|
|
||||||
const { dirs } = scope
|
|
||||||
// beforeUpdate hook
|
|
||||||
if (bu) {
|
|
||||||
invokeArrayFns(bu)
|
|
||||||
}
|
|
||||||
invokeDirectiveHook(instance, 'beforeUpdate', scope)
|
|
||||||
|
|
||||||
queuePostFlushCb(() => {
|
|
||||||
instance.isUpdating = false
|
|
||||||
const reset = setCurrentInstance(instance)
|
|
||||||
if (dirs) {
|
|
||||||
invokeDirectiveHook(instance, 'updated', scope)
|
|
||||||
}
|
|
||||||
// updated hook
|
|
||||||
if (u) {
|
|
||||||
queuePostFlushCb(u)
|
|
||||||
}
|
|
||||||
reset()
|
|
||||||
})
|
|
||||||
reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,155 +0,0 @@
|
||||||
import { ReactiveEffect, getCurrentScope } from '@vue/reactivity'
|
|
||||||
import {
|
|
||||||
type Directive,
|
|
||||||
type DirectiveHookName,
|
|
||||||
createRenderingUpdateTrigger,
|
|
||||||
invokeDirectiveHook,
|
|
||||||
} from './directives'
|
|
||||||
import { warn } from './warning'
|
|
||||||
import { type BlockEffectScope, isRenderEffectScope } from './blockEffectScope'
|
|
||||||
import { currentInstance } from './component'
|
|
||||||
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
|
|
||||||
import { queueJob, queuePostFlushCb } from './scheduler'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* used in createIf and createFor
|
|
||||||
* manage directives of child fragments in components.
|
|
||||||
*/
|
|
||||||
export function createChildFragmentDirectives(
|
|
||||||
anchor: Node,
|
|
||||||
getScopes: () => BlockEffectScope[],
|
|
||||||
source: () => any,
|
|
||||||
initCallback: (getValue: () => any) => void,
|
|
||||||
effectCallback: (getValue: () => any) => void,
|
|
||||||
once?: boolean,
|
|
||||||
): void {
|
|
||||||
let isTriggered = false
|
|
||||||
const instance = currentInstance!
|
|
||||||
const parentScope = getCurrentScope() as BlockEffectScope
|
|
||||||
if (__DEV__) {
|
|
||||||
if (!isRenderEffectScope(parentScope)) {
|
|
||||||
warn('child directives can only be added to a render effect scope')
|
|
||||||
}
|
|
||||||
if (!instance) {
|
|
||||||
warn('child directives can only be added in a component')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const callSourceWithErrorHandling = () =>
|
|
||||||
callWithErrorHandling(source, instance, VaporErrorCodes.RENDER_FUNCTION)
|
|
||||||
|
|
||||||
if (once) {
|
|
||||||
initCallback(callSourceWithErrorHandling)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const directiveBindingsMap = (parentScope.dirs ||= new Map())
|
|
||||||
const dir: Directive = {
|
|
||||||
beforeUpdate: onDirectiveBeforeUpdate,
|
|
||||||
beforeMount: () => invokeChildrenDirectives('beforeMount'),
|
|
||||||
mounted: () => invokeChildrenDirectives('mounted'),
|
|
||||||
beforeUnmount: () => invokeChildrenDirectives('beforeUnmount'),
|
|
||||||
unmounted: () => invokeChildrenDirectives('unmounted'),
|
|
||||||
}
|
|
||||||
directiveBindingsMap.set(anchor, [
|
|
||||||
{
|
|
||||||
dir,
|
|
||||||
instance,
|
|
||||||
value: null,
|
|
||||||
oldValue: undefined,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
const effect = new ReactiveEffect(callSourceWithErrorHandling)
|
|
||||||
const triggerRenderingUpdate = createRenderingUpdateTrigger(instance, effect)
|
|
||||||
effect.scheduler = () => {
|
|
||||||
isTriggered = true
|
|
||||||
queueJob(triggerRenderingUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getValue = () => effect.run()
|
|
||||||
|
|
||||||
initCallback(getValue)
|
|
||||||
|
|
||||||
function onDirectiveBeforeUpdate() {
|
|
||||||
if (isTriggered) {
|
|
||||||
isTriggered = false
|
|
||||||
effectCallback(getValue)
|
|
||||||
} else {
|
|
||||||
const scopes = getScopes()
|
|
||||||
for (const scope of scopes) {
|
|
||||||
invokeWithUpdate(scope)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function invokeChildrenDirectives(name: DirectiveHookName) {
|
|
||||||
const scopes = getScopes()
|
|
||||||
for (const scope of scopes) {
|
|
||||||
invokeDirectiveHook(instance, name, scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function invokeWithMount(
|
|
||||||
scope: BlockEffectScope,
|
|
||||||
handler?: () => any,
|
|
||||||
): any {
|
|
||||||
if (isRenderEffectScope(scope.parent) && !scope.parent.im) {
|
|
||||||
return handler && handler()
|
|
||||||
}
|
|
||||||
return invokeWithDirsHooks(scope, 'mount', handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function invokeWithUnmount(
|
|
||||||
scope: BlockEffectScope,
|
|
||||||
handler?: () => void,
|
|
||||||
): any {
|
|
||||||
try {
|
|
||||||
return invokeWithDirsHooks(scope, 'unmount', handler)
|
|
||||||
} finally {
|
|
||||||
scope.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function invokeWithUpdate(
|
|
||||||
scope: BlockEffectScope,
|
|
||||||
handler?: () => void,
|
|
||||||
): any {
|
|
||||||
return invokeWithDirsHooks(scope, 'update', handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
const lifecycleMap = {
|
|
||||||
mount: ['beforeMount', 'mounted'],
|
|
||||||
update: ['beforeUpdate', 'updated'],
|
|
||||||
unmount: ['beforeUnmount', 'unmounted'],
|
|
||||||
} as const
|
|
||||||
|
|
||||||
function invokeWithDirsHooks(
|
|
||||||
scope: BlockEffectScope,
|
|
||||||
name: keyof typeof lifecycleMap,
|
|
||||||
handler?: () => any,
|
|
||||||
) {
|
|
||||||
const { dirs, it: instance } = scope
|
|
||||||
const [before, after] = lifecycleMap[name]
|
|
||||||
|
|
||||||
if (!dirs) {
|
|
||||||
const res = handler && handler()
|
|
||||||
if (name === 'mount') {
|
|
||||||
queuePostFlushCb(() => (scope.im = true))
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
invokeDirectiveHook(instance, before, scope)
|
|
||||||
try {
|
|
||||||
if (handler) {
|
|
||||||
return handler()
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
queuePostFlushCb(() => {
|
|
||||||
invokeDirectiveHook(instance, after, scope)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,7 +12,6 @@ import {
|
||||||
queuePostFlushCb,
|
queuePostFlushCb,
|
||||||
} from './scheduler'
|
} from './scheduler'
|
||||||
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
||||||
import { invokeDirectiveHook } from './directives'
|
|
||||||
|
|
||||||
export function renderEffect(cb: () => void): void {
|
export function renderEffect(cb: () => void): void {
|
||||||
const instance = getCurrentInstance()
|
const instance = getCurrentInstance()
|
||||||
|
@ -58,24 +57,17 @@ export function renderEffect(cb: () => void): void {
|
||||||
if (instance && instance.isMounted && !instance.isUpdating) {
|
if (instance && instance.isMounted && !instance.isUpdating) {
|
||||||
instance.isUpdating = true
|
instance.isUpdating = true
|
||||||
|
|
||||||
const { bu, u, scope } = instance
|
const { bu, u } = instance
|
||||||
const { dirs } = scope
|
|
||||||
// beforeUpdate hook
|
// beforeUpdate hook
|
||||||
if (bu) {
|
if (bu) {
|
||||||
invokeArrayFns(bu)
|
invokeArrayFns(bu)
|
||||||
}
|
}
|
||||||
if (dirs) {
|
|
||||||
invokeDirectiveHook(instance, 'beforeUpdate', scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
effect.run()
|
effect.run()
|
||||||
|
|
||||||
queuePostFlushCb(() => {
|
queuePostFlushCb(() => {
|
||||||
instance.isUpdating = false
|
instance.isUpdating = false
|
||||||
const reset = setCurrentInstance(instance)
|
const reset = setCurrentInstance(instance)
|
||||||
if (dirs) {
|
|
||||||
invokeDirectiveHook(instance, 'updated', scope)
|
|
||||||
}
|
|
||||||
// updated hook
|
// updated hook
|
||||||
if (u) {
|
if (u) {
|
||||||
queuePostFlushCb(u)
|
queuePostFlushCb(u)
|
||||||
|
|
Loading…
Reference in New Issue