]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(directives): add support for function directives (#252)
authorDmitry Sharshakov <d3dx12.xx@gmail.com>
Wed, 16 Oct 2019 06:12:26 +0000 (09:12 +0300)
committerEvan You <yyx990803@gmail.com>
Wed, 16 Oct 2019 06:12:26 +0000 (02:12 -0400)
packages/runtime-core/__tests__/directives.spec.ts
packages/runtime-core/src/directives.ts
packages/runtime-core/src/index.ts
packages/runtime-dom/src/directives/vModel.ts

index 4ce5e577e34401569d841480c32ea0b096cae53f..e2bbf04f1966e97b8affa8cb6536de672d31648e 100644 (file)
@@ -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)
+  })
 })
index e3ee28b7e2a6de3d87fc4b21083bcd8acaa0d310..1ab1b0d806170a2cc03b802d93c09040395748e8 100644 (file)
@@ -34,7 +34,7 @@ export type DirectiveHook<T = any> = (
   prevVNode: VNode<any, T> | null
 ) => void
 
-export interface Directive<T = any> {
+export interface ObjectDirective<T = any> {
   beforeMount?: DirectiveHook<T>
   mounted?: DirectiveHook<T>
   beforeUpdate?: DirectiveHook<T>
@@ -43,6 +43,10 @@ export interface Directive<T = any> {
   unmounted?: DirectiveHook<T>
 }
 
+export type FunctionDirective<T = any> = DirectiveHook<T>
+
+export type Directive<T = any> = ObjectDirective<T> | FunctionDirective<T>
+
 type DirectiveModifiers = Record<string, boolean>
 
 const valueCache = new WeakMap<Directive, WeakMap<any, any>>()
@@ -60,8 +64,16 @@ function applyDirective(
     valueCacheForDir = new WeakMap<VNode, any>()
     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
index 3df496807f79a4a31e763a69f887c114714d0b6f..3e4abf82b1b5cba2569e9b10d3e9dd2be9f302d0 100644 (file)
@@ -81,6 +81,8 @@ export {
   Directive,
   DirectiveBinding,
   DirectiveHook,
+  ObjectDirective,
+  FunctionDirective,
   DirectiveArguments
 } from './directives'
 export { SuspenseBoundary } from './suspense'
index 1df9a05ed5fdd552f4f81f2dc3c23aab518b1cfe..65802a6014664e930124d888108229a1f18484c8 100644 (file)
@@ -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<HTMLInputElement | HTMLTextAreaElement> = {
+export const vModelText: ObjectDirective<HTMLInputElement | HTMLTextAreaElement> = {
   beforeMount(el, { value, modifiers: { lazy, trim, number } }, vnode) {
     el.value = value
     const assign = getModelAssigner(vnode)
@@ -72,7 +77,7 @@ export const vModelText: Directive<HTMLInputElement | HTMLTextAreaElement> = {
   }
 }
 
-export const vModelCheckbox: Directive<HTMLInputElement> = {
+export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
   beforeMount(el, binding, vnode) {
     setChecked(el, binding, vnode)
     const assign = getModelAssigner(vnode)
@@ -111,7 +116,7 @@ function setChecked(
     : !!value
 }
 
-export const vModelRadio: Directive<HTMLInputElement> = {
+export const vModelRadio: ObjectDirective<HTMLInputElement> = {
   beforeMount(el, { value }, vnode) {
     el.checked = looseEqual(value, vnode.props!.value)
     const assign = getModelAssigner(vnode)
@@ -124,7 +129,7 @@ export const vModelRadio: Directive<HTMLInputElement> = {
   }
 }
 
-export const vModelSelect: Directive<HTMLSelectElement> = {
+export const vModelSelect: ObjectDirective<HTMLSelectElement> = {
   // use mounted & updated because <select> relies on its children <option>s.
   mounted(el, { value }, vnode) {
     setSelected(el, value)
@@ -214,7 +219,7 @@ function getValue(el: HTMLOptionElement | HTMLInputElement) {
   return '_value' in el ? (el as any)._value : el.value
 }
 
-export const vModelDynamic: Directive<
+export const vModelDynamic: ObjectDirective<
   HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
 > = {
   beforeMount(el, binding, vnode) {
@@ -236,9 +241,9 @@ function callModelHook(
   binding: DirectiveBinding,
   vnode: VNode,
   prevVNode: VNode | null,
-  hook: keyof Directive
+  hook: keyof ObjectDirective
 ) {
-  let modelToUse: Directive
+  let modelToUse: ObjectDirective
   switch (el.tagName) {
     case 'SELECT':
       modelToUse = vModelSelect