Просмотр исходного кода

refactor directive update implementation

Evan You 9 лет назад
Родитель
Сommit
1fa3844dc2

+ 2 - 0
flow/vnode.js

@@ -56,8 +56,10 @@ declare interface VNodeData {
 
 declare type VNodeDirective = {
   name: string;
+  rawName: string;
   value?: any;
   oldValue?: any;
   arg?: string;
   modifiers?: { [key: string]: boolean };
+  def?: Object;
 }

+ 1 - 1
src/compiler/codegen/index.js

@@ -187,7 +187,7 @@ function genDirectives (el: ASTElement): string | void {
     }
     if (needRuntime) {
       hasRuntime = true
-      res += `{name:"${dir.name}"${
+      res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
         dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''
       }${
         dir.arg ? `,arg:"${dir.arg}"` : ''

+ 2 - 1
src/compiler/helpers.js

@@ -24,11 +24,12 @@ export function addAttr (el: ASTElement, name: string, value: string) {
 export function addDirective (
   el: ASTElement,
   name: string,
+  rawName: string,
   value: string,
   arg: ?string,
   modifiers: ?{ [key: string]: true }
 ) {
-  (el.directives || (el.directives = [])).push({ name, value, arg, modifiers })
+  (el.directives || (el.directives = [])).push({ name, rawName, value, arg, modifiers })
 }
 
 export function addHandler (

+ 3 - 3
src/compiler/parser/index.js

@@ -353,9 +353,9 @@ function processComponent (el) {
 
 function processAttrs (el) {
   const list = el.attrsList
-  let i, l, name, value, arg, modifiers, isProp
+  let i, l, name, rawName, value, arg, modifiers, isProp
   for (i = 0, l = list.length; i < l; i++) {
-    name = list[i].name
+    name = rawName = list[i].name
     value = list[i].value
     if (dirRE.test(name)) {
       // mark element as dynamic
@@ -387,7 +387,7 @@ function processAttrs (el) {
         if (argMatch && (arg = argMatch[1])) {
           name = name.slice(0, -(arg.length + 1))
         }
-        addDirective(el, name, value, arg, modifiers)
+        addDirective(el, name, rawName, value, arg, modifiers)
         if (process.env.NODE_ENV !== 'production' && name === 'model') {
           checkForAliasModel(el, value)
         }

+ 93 - 54
src/core/vdom/modules/directives.js

@@ -2,76 +2,115 @@
 
 import { resolveAsset } from 'core/util/options'
 import { mergeVNodeHook } from 'core/vdom/helpers'
+import { emptyNode } from 'core/vdom/patch'
 
 export default {
-  create: function bindDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {
-    let hasInsert = false
-    forEachDirective(oldVnode, vnode, (def, dir) => {
-      callHook(def, dir, 'bind', vnode, oldVnode)
-      if (def.inserted) {
-        hasInsert = true
+  create: updateDirectives,
+  update: updateDirectives,
+  destroy: function unbindDirectives (vnode: VNodeWithData) {
+    updateDirectives(vnode, emptyNode)
+  }
+}
+
+function updateDirectives (
+  oldVnode: VNodeWithData,
+  vnode: VNodeWithData
+) {
+  if (!oldVnode.data.directives && !vnode.data.directives) {
+    return
+  }
+  const isCreate = oldVnode === emptyNode
+  const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context)
+  const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)
+
+  const dirsWithInsert = []
+  const dirsWithPostpatch = []
+
+  let key, oldDir, dir
+  for (key in newDirs) {
+    oldDir = oldDirs[key]
+    dir = newDirs[key]
+    if (!oldDir) {
+      // new directive, bind
+      callHook(dir, 'bind', vnode, oldVnode)
+      if (dir.def && dir.def.inserted) {
+        dirsWithInsert.push(dir)
+      }
+    } else {
+      // existing directive, update
+      dir.oldValue = oldDir.value
+      callHook(dir, 'update', vnode, oldVnode)
+      if (dir.def && dir.def.componentUpdated) {
+        dirsWithPostpatch.push(dir)
       }
-    })
-    if (hasInsert) {
-      mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', () => {
-        applyDirectives(oldVnode, vnode, 'inserted')
-      }, 'dir-insert')
     }
-  },
-  update: function updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {
-    applyDirectives(oldVnode, vnode, 'update')
-    // if old vnode has directives but new vnode doesn't
-    // we need to teardown the directives on the old one.
-    if (oldVnode.data.directives && !vnode.data.directives) {
-      applyDirectives(oldVnode, oldVnode, 'unbind')
+  }
+
+  if (dirsWithInsert.length) {
+    const callInsert = () => {
+      dirsWithInsert.forEach(dir => {
+        callHook(dir, 'inserted', vnode, oldVnode)
+      })
+    }
+    if (isCreate) {
+      mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', callInsert, 'dir-insert')
+    } else {
+      callInsert()
+    }
+  }
+
+  if (dirsWithPostpatch.length) {
+    mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'postpatch', () => {
+      dirsWithPostpatch.forEach(dir => {
+        callHook(dir, 'componentUpdated', vnode, oldVnode)
+      })
+    }, 'dir-postpatch')
+  }
+
+  if (!isCreate) {
+    for (key in oldDirs) {
+      if (!newDirs[key]) {
+        // no longer present, unbind
+        callHook(oldDirs[key], 'unbind', oldVnode)
+      }
     }
-  },
-  postpatch: function postupdateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {
-    applyDirectives(oldVnode, vnode, 'componentUpdated')
-  },
-  destroy: function unbindDirectives (vnode: VNodeWithData) {
-    applyDirectives(vnode, vnode, 'unbind')
   }
 }
 
 const emptyModifiers = Object.create(null)
 
-function forEachDirective (
-  oldVnode: VNodeWithData,
-  vnode: VNodeWithData,
-  fn: Function
-) {
-  const dirs = vnode.data.directives
-  if (dirs) {
-    for (let i = 0; i < dirs.length; i++) {
-      const dir = dirs[i]
-      const def = resolveAsset(vnode.context.$options, 'directives', dir.name, true)
-      if (def) {
-        const oldDirs = oldVnode && oldVnode.data.directives
-        if (oldDirs) {
-          dir.oldValue = oldDirs[i].value
-        }
-        if (!dir.modifiers) {
-          dir.modifiers = emptyModifiers
-        }
-        fn(def, dir)
-      }
+function normalizeDirectives (
+  dirs: ?Array<VNodeDirective>,
+  vm: Component
+): { [key: string]: VNodeDirective } {
+  const res = Object.create(null)
+  if (!dirs) {
+    return res
+  }
+  let i, dir
+  for (i = 0; i < dirs.length; i++) {
+    dir = dirs[i]
+    res[getRawDirName(dir)] = dir
+    if (!dir.modifiers) {
+      dir.modifiers = emptyModifiers
     }
+    dir.def = resolveAsset(vm.$options, 'directives', dir.name, true)
   }
+  return res
 }
 
-function applyDirectives (
-  oldVnode: VNodeWithData,
-  vnode: VNodeWithData,
-  hook: string
-) {
-  forEachDirective(oldVnode, vnode, (def, dir) => {
-    callHook(def, dir, hook, vnode, oldVnode)
-  })
+function getRawDirName (dir: VNodeDirective): string {
+  return dir.rawName || (
+    dir.name + (
+      dir.modifiers
+        ? '.' + Object.keys(dir.modifiers).join('.')
+        : ''
+    )
+  )
 }
 
-function callHook (def, dir, hook, vnode, oldVnode) {
-  const fn = def && def[hook]
+function callHook (dir, hook, vnode, oldVnode) {
+  const fn = dir.def && dir.def[hook]
   if (fn) {
     fn(vnode.elm, dir, vnode, oldVnode)
   }

+ 9 - 9
src/core/vdom/patch.js

@@ -18,9 +18,9 @@ import { isPrimitive, _toString, warn } from '../util/index'
 import { activeInstance } from '../instance/lifecycle'
 import { registerRef } from './modules/ref'
 
-const emptyData = {}
-const emptyNode = new VNode('', emptyData, [])
-const hooks = ['create', 'update', 'postpatch', 'remove', 'destroy']
+export const emptyNode = new VNode('', {}, [])
+
+const hooks = ['create', 'update', 'remove', 'destroy']
 
 function isUndef (s) {
   return s == null
@@ -344,9 +344,10 @@ export function createPatchFunction (backend) {
       vnode.elm = oldVnode.elm
       return
     }
-    let i, hook
-    const hasData = isDef(i = vnode.data)
-    if (hasData && isDef(hook = i.hook) && isDef(i = hook.prepatch)) {
+    let i
+    const data = vnode.data
+    const hasData = isDef(data)
+    if (hasData && isDef(i = data.hook) && isDef(i = i.prepatch)) {
       i(oldVnode, vnode)
     }
     const elm = vnode.elm = oldVnode.elm
@@ -354,7 +355,7 @@ export function createPatchFunction (backend) {
     const ch = vnode.children
     if (hasData && isPatchable(vnode)) {
       for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
-      if (isDef(hook) && isDef(i = hook.update)) i(oldVnode, vnode)
+      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
     }
     if (isUndef(vnode.text)) {
       if (isDef(oldCh) && isDef(ch)) {
@@ -371,8 +372,7 @@ export function createPatchFunction (backend) {
       nodeOps.setTextContent(elm, vnode.text)
     }
     if (hasData) {
-      for (i = 0; i < cbs.postpatch.length; ++i) cbs.postpatch[i](oldVnode, vnode)
-      if (isDef(hook) && isDef(i = hook.postpatch)) i(oldVnode, vnode)
+      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
     }
   }
 

+ 3 - 3
test/unit/modules/compiler/codegen.spec.js

@@ -32,8 +32,8 @@ function assertCodegen (template, generatedCode, ...args) {
 describe('codegen', () => {
   it('generate directive', () => {
     assertCodegen(
-      '<p v-custom1:arg1.modifire="value1" v-custom2><p>',
-      `with(this){return _h('p',{directives:[{name:"custom1",value:(value1),expression:"value1",arg:"arg1",modifiers:{"modifire":true}},{name:"custom2",arg:"arg1"}]})}`
+      '<p v-custom1:arg1.modifier="value1" v-custom2><p>',
+      `with(this){return _h('p',{directives:[{name:"custom1",rawName:"v-custom1:arg1.modifier",value:(value1),expression:"value1",arg:"arg1",modifiers:{"modifier":true}},{name:"custom2",rawName:"v-custom2",arg:"arg1"}]})}`
     )
   })
 
@@ -148,7 +148,7 @@ describe('codegen', () => {
   it('generate v-show directive', () => {
     assertCodegen(
       '<p v-show="shown">hello world</p>',
-      `with(this){return _h('p',{directives:[{name:"show",value:(shown),expression:"shown"}]},["hello world"])}`
+      `with(this){return _h('p',{directives:[{name:"show",rawName:"v-show",value:(shown),expression:"shown"}]},["hello world"])}`
     )
   })