From: Dmitry Sharshakov Date: Wed, 16 Oct 2019 06:12:26 +0000 (+0300) Subject: feat(directives): add support for function directives (#252) X-Git-Tag: v3.0.0-alpha.0~406 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0bac763f5a4906a030258dd3a71a912c0a0b1201;p=thirdparty%2Fvuejs%2Fcore.git feat(directives): add support for function directives (#252) --- diff --git a/packages/runtime-core/__tests__/directives.spec.ts b/packages/runtime-core/__tests__/directives.spec.ts index 4ce5e577e3..e2bbf04f19 100644 --- a/packages/runtime-core/__tests__/directives.spec.ts +++ b/packages/runtime-core/__tests__/directives.spec.ts @@ -144,4 +144,58 @@ describe('directives', () => { expect(beforeUnmount).toHaveBeenCalled() expect(unmounted).toHaveBeenCalled() }) + + it('should work with a function directive', async () => { + const count = ref(0) + + function assertBindings(binding: DirectiveBinding) { + expect(binding.value).toBe(count.value) + expect(binding.arg).toBe('foo') + expect(binding.instance).toBe(_instance && _instance.renderProxy) + expect(binding.modifiers && binding.modifiers.ok).toBe(true) + } + + const fn = jest.fn(((el, binding, vnode, prevVNode) => { + expect(el.tag).toBe('div') + expect(el.parentNode).toBe(root) + + assertBindings(binding) + + expect(vnode).toBe(_vnode) + expect(prevVNode).toBe(_prevVnode) + }) as DirectiveHook) + + let _instance: ComponentInternalInstance | null = null + let _vnode: VNode | null = null + let _prevVnode: VNode | null = null + const Comp = { + setup() { + _instance = currentInstance + }, + render() { + _prevVnode = _vnode + _vnode = applyDirectives(h('div', count.value), [ + [ + fn, + // value + count.value, + // argument + 'foo', + // modifiers + { ok: true } + ] + ]) + return _vnode + } + } + + const root = nodeOps.createElement('div') + render(h(Comp), root) + + expect(fn).toHaveBeenCalledTimes(1) + + count.value++ + await nextTick() + expect(fn).toHaveBeenCalledTimes(2) + }) }) diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts index e3ee28b7e2..1ab1b0d806 100644 --- a/packages/runtime-core/src/directives.ts +++ b/packages/runtime-core/src/directives.ts @@ -34,7 +34,7 @@ export type DirectiveHook = ( prevVNode: VNode | null ) => void -export interface Directive { +export interface ObjectDirective { beforeMount?: DirectiveHook mounted?: DirectiveHook beforeUpdate?: DirectiveHook @@ -43,6 +43,10 @@ export interface Directive { unmounted?: DirectiveHook } +export type FunctionDirective = DirectiveHook + +export type Directive = ObjectDirective | FunctionDirective + type DirectiveModifiers = Record const valueCache = new WeakMap>() @@ -60,8 +64,16 @@ function applyDirective( valueCacheForDir = new WeakMap() valueCache.set(directive, valueCacheForDir) } + + if (isFunction(directive)) { + directive = { + mounted: directive, + updated: directive + } as ObjectDirective + } + for (const key in directive) { - const hook = directive[key as keyof Directive]! + const hook = directive[key as keyof ObjectDirective]! const hookKey = `vnode` + key[0].toUpperCase() + key.slice(1) const vnodeHook = (vnode: VNode, prevVNode: VNode | null) => { let oldValue diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 3df496807f..3e4abf82b1 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -81,6 +81,8 @@ export { Directive, DirectiveBinding, DirectiveHook, + ObjectDirective, + FunctionDirective, DirectiveArguments } from './directives' export { SuspenseBoundary } from './suspense' diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts index 1df9a05ed5..65802a6014 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -1,4 +1,9 @@ -import { Directive, VNode, DirectiveBinding, warn } from '@vue/runtime-core' +import { + ObjectDirective, + VNode, + DirectiveBinding, + warn +} from '@vue/runtime-core' import { addEventListener } from '../modules/events' import { isArray, isObject } from '@vue/shared' @@ -30,7 +35,7 @@ function toNumber(val: string): number | string { // We are exporting the v-model runtime directly as vnode hooks so that it can // be tree-shaken in case v-model is never used. -export const vModelText: Directive = { +export const vModelText: ObjectDirective = { beforeMount(el, { value, modifiers: { lazy, trim, number } }, vnode) { el.value = value const assign = getModelAssigner(vnode) @@ -72,7 +77,7 @@ export const vModelText: Directive = { } } -export const vModelCheckbox: Directive = { +export const vModelCheckbox: ObjectDirective = { beforeMount(el, binding, vnode) { setChecked(el, binding, vnode) const assign = getModelAssigner(vnode) @@ -111,7 +116,7 @@ function setChecked( : !!value } -export const vModelRadio: Directive = { +export const vModelRadio: ObjectDirective = { beforeMount(el, { value }, vnode) { el.checked = looseEqual(value, vnode.props!.value) const assign = getModelAssigner(vnode) @@ -124,7 +129,7 @@ export const vModelRadio: Directive = { } } -export const vModelSelect: Directive = { +export const vModelSelect: ObjectDirective = { // use mounted & updated because