Browse Source

refactor directive update implementation

dev
Evan You 8 years ago
parent
commit
1fa3844dc2
  1. 2
      flow/vnode.js
  2. 2
      src/compiler/codegen/index.js
  3. 3
      src/compiler/helpers.js
  4. 6
      src/compiler/parser/index.js
  5. 147
      src/core/vdom/modules/directives.js
  6. 18
      src/core/vdom/patch.js
  7. 6
      test/unit/modules/compiler/codegen.spec.js

2
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;
}

2
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}"` : ''

3
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 (

6
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)
}

147
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)
}

18
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)
}
}

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

Loading…
Cancel
Save