From 1fa3844dc26745b4cb855b0934bd1b132ba33d9b Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 11 Oct 2016 20:31:13 -0400 Subject: [PATCH] refactor directive update implementation --- flow/vnode.js | 2 + src/compiler/codegen/index.js | 2 +- src/compiler/helpers.js | 3 +- src/compiler/parser/index.js | 6 +- src/core/vdom/modules/directives.js | 147 +++++++++++++-------- src/core/vdom/patch.js | 18 +-- test/unit/modules/compiler/codegen.spec.js | 6 +- 7 files changed, 113 insertions(+), 71 deletions(-) diff --git a/flow/vnode.js b/flow/vnode.js index db1e0525..39d90f78 100644 --- a/flow/vnode.js +++ b/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; } diff --git a/src/compiler/codegen/index.js b/src/compiler/codegen/index.js index 1de4605f..d11fc7e3 100644 --- a/src/compiler/codegen/index.js +++ b/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}"` : '' diff --git a/src/compiler/helpers.js b/src/compiler/helpers.js index 86d23e41..652ea974 100644 --- a/src/compiler/helpers.js +++ b/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 ( diff --git a/src/compiler/parser/index.js b/src/compiler/parser/index.js index f36e8524..b41fdf4c 100644 --- a/src/compiler/parser/index.js +++ b/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) } diff --git a/src/core/vdom/modules/directives.js b/src/core/vdom/modules/directives.js index c3c4541c..513afe51 100644 --- a/src/core/vdom/modules/directives.js +++ b/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, + 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) } diff --git a/src/core/vdom/patch.js b/src/core/vdom/patch.js index b5d89e02..f655a7b4 100644 --- a/src/core/vdom/patch.js +++ b/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) } } diff --git a/test/unit/modules/compiler/codegen.spec.js b/test/unit/modules/compiler/codegen.spec.js index ad0f27e0..99359f5e 100644 --- a/test/unit/modules/compiler/codegen.spec.js +++ b/test/unit/modules/compiler/codegen.spec.js @@ -32,8 +32,8 @@ function assertCodegen (template, generatedCode, ...args) { describe('codegen', () => { it('generate directive', () => { assertCodegen( - '

', - `with(this){return _h('p',{directives:[{name:"custom1",value:(value1),expression:"value1",arg:"arg1",modifiers:{"modifire":true}},{name:"custom2",arg:"arg1"}]})}` + '

', + `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( '

hello world

', - `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"])}` ) })