From ef8c4f61a225861bb7f490c0b304f724c4ef0891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Thu, 19 Sep 2024 19:28:04 +0800 Subject: [PATCH] feat: custom directive v2 --- packages/runtime-vapor/src/directives.ts | 96 ++++++++++++------- .../runtime-vapor/src/directives/vShow.ts | 41 ++++---- packages/runtime-vapor/src/index.ts | 2 - 3 files changed, 88 insertions(+), 51 deletions(-) diff --git a/packages/runtime-vapor/src/directives.ts b/packages/runtime-vapor/src/directives.ts index b6a09c322..4691d51de 100644 --- a/packages/runtime-vapor/src/directives.ts +++ b/packages/runtime-vapor/src/directives.ts @@ -1,54 +1,37 @@ import { isBuiltInDirective } from '@vue/shared' -import { type ComponentInternalInstance, currentInstance } from './component' +import { + type ComponentInternalInstance, + currentInstance, + isVaporComponent, +} from './component' import { warn } from './warning' +import { normalizeBlock } from './dom/element' +import { getCurrentScope } from '@vue/reactivity' +import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling' export type DirectiveModifiers = Record export interface DirectiveBinding { instance: ComponentInternalInstance source?: () => V - value: V - oldValue: V | null arg?: string modifiers?: DirectiveModifiers - dir: ObjectDirective + dir: Directive } export type DirectiveBindingsMap = Map -export type DirectiveHook< - T = any | null, - V = any, - M extends string = string, -> = (node: T, binding: DirectiveBinding) => void - -// create node -> `created` -> node operation -> `beforeMount` -> node mounted -> `mounted` -// effect update -> `beforeUpdate` -> node updated -> `updated` -// `beforeUnmount`-> node unmount -> `unmounted` -export type DirectiveHookName = - | 'created' - | 'beforeMount' - | 'mounted' - | 'beforeUpdate' - | 'updated' - | 'beforeUnmount' - | 'unmounted' -export type ObjectDirective = { - [K in DirectiveHookName]?: DirectiveHook | undefined -} & { - /** Watch value deeply */ - deep?: boolean | number -} - -export type FunctionDirective< +export type Directive< T = any, V = any, M extends string = string, > = DirectiveHook -export type Directive = - | ObjectDirective - | FunctionDirective +export type DirectiveHook< + T = any | null, + V = any, + M extends string = string, +> = (node: T, binding: DirectiveBinding) => void export function validateDirectiveName(name: string): void { if (isBuiltInDirective(name)) { @@ -77,7 +60,54 @@ export function withDirectives( return nodeOrComponent } - // NOOP + let node: Node + if (isVaporComponent(nodeOrComponent)) { + const root = getComponentNode(nodeOrComponent) + if (!root) return nodeOrComponent + node = root + } else { + node = nodeOrComponent + } + + const instance = currentInstance! + const parentScope = getCurrentScope() + + if (__DEV__ && !parentScope) { + warn(`Directives should be used inside of RenderEffectScope.`) + } + + for (const directive of directives) { + let [dir, source, arg, modifiers] = directive + if (!dir) continue + + const binding: DirectiveBinding = { + dir, + source, + instance, + arg, + modifiers, + } + + callWithAsyncErrorHandling(dir, instance, VaporErrorCodes.DIRECTIVE_HOOK, [ + node, + binding, + ]) + } 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] +} diff --git a/packages/runtime-vapor/src/directives/vShow.ts b/packages/runtime-vapor/src/directives/vShow.ts index 80a22d501..8176ec985 100644 --- a/packages/runtime-vapor/src/directives/vShow.ts +++ b/packages/runtime-vapor/src/directives/vShow.ts @@ -1,23 +1,32 @@ -import type { ObjectDirective } from '../directives' +import { onBeforeMount } from '../apiLifecycle' +import type { Directive } from '../directives' +import { renderEffect } from '../renderEffect' -const vShowMap = new WeakMap() +export const vShowOriginalDisplay: unique symbol = Symbol('_vod') +export const vShowHidden: unique symbol = Symbol('_vsh') -export const vShow: ObjectDirective = { - beforeMount(node, { value }) { - vShowMap.set(node, node.style.display === 'none' ? '' : node.style.display) - setDisplay(node, value) - }, +export interface VShowElement extends HTMLElement { + // _vod = vue original display + [vShowOriginalDisplay]: string + [vShowHidden]: boolean +} + +export const vShow: Directive = (node, { source }) => { + function getValue(): boolean { + return source ? source() : false + } - updated(node, { value, oldValue }) { - if (!value === !oldValue) return - setDisplay(node, value) - }, + onBeforeMount(() => { + node[vShowOriginalDisplay] = + node.style.display === 'none' ? '' : node.style.display + }) - beforeUnmount(node, { value }) { - setDisplay(node, value) - }, + renderEffect(() => { + setDisplay(node, getValue()) + }) } -function setDisplay(el: HTMLElement, value: unknown): void { - el.style.display = value ? vShowMap.get(el)! : 'none' +function setDisplay(el: VShowElement, value: unknown): void { + el.style.display = value ? el[vShowOriginalDisplay] : 'none' + el[vShowHidden] = !value } diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 17c725c5e..44f3c3261 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -77,8 +77,6 @@ export { type Directive, type DirectiveBinding, type DirectiveHook, - type ObjectDirective, - type FunctionDirective, type DirectiveArguments, type DirectiveModifiers, } from './directives'