diff --git a/flow/component.js b/flow/component.js index 49aebbe7..aae1103a 100644 --- a/flow/component.js +++ b/flow/component.js @@ -2,6 +2,10 @@ import type { Config } from '../src/core/config' import type VNode from '../src/core/vdom/vnode' import type Watcher from '../src/core/observer/watcher' +declare type Refs = { + [key: string]: Component | Element | Array | void; +}; + declare interface Component { // constructor information static cid: number; @@ -23,7 +27,7 @@ declare interface Component { $parent: Component | void; $root: Component; $children: Array; - $refs: { [key: string]: Component | Element | Array | void }; + $refs: Refs; $slots: { [key: string]: Array }; $scopedSlots: { [key: string]: () => VNodeChildren }; $vnode: VNode; diff --git a/src/core/instance/lifecycle.js b/src/core/instance/lifecycle.js index efe7ad64..b42b8bf7 100644 --- a/src/core/instance/lifecycle.js +++ b/src/core/instance/lifecycle.js @@ -1,6 +1,7 @@ /* @flow */ import Watcher from '../observer/watcher' +import { resetRefs } from '../vdom/modules/ref' import { createEmptyVNode } from '../vdom/vnode' import { observerState } from '../observer/index' import { updateComponentListeners } from './events' @@ -83,6 +84,7 @@ export function lifecycleMixin (Vue: Class) { const prevVnode = vm._vnode const prevActiveInstance = activeInstance activeInstance = vm + vm.$refs = resetRefs(vm.$refs) vm._vnode = vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. diff --git a/src/core/vdom/modules/ref.js b/src/core/vdom/modules/ref.js index c69547ce..d700e9e5 100644 --- a/src/core/vdom/modules/ref.js +++ b/src/core/vdom/modules/ref.js @@ -1,44 +1,40 @@ /* @flow */ -import { remove } from 'shared/util' - export default { - create (_: any, vnode: VNodeWithData) { - registerRef(vnode) - }, - update (oldVnode: VNodeWithData, vnode: VNodeWithData) { - if (oldVnode.data.ref !== vnode.data.ref) { - registerRef(oldVnode, true) - registerRef(vnode) - } - }, - destroy (vnode: VNodeWithData) { - registerRef(vnode, true) - } + create: registerRef, + update: registerRef } -export function registerRef (vnode: VNodeWithData, isRemoval: ?boolean) { +export function registerRef (_: any, vnode: VNodeWithData) { const key = vnode.data.ref if (!key) return - const vm = vnode.context const ref = vnode.componentInstance || vnode.elm - const refs = vm.$refs - if (isRemoval) { - if (Array.isArray(refs[key])) { - remove(refs[key], ref) - } else if (refs[key] === ref) { - refs[key] = undefined - } - } else { - if (vnode.data.refInFor) { - if (Array.isArray(refs[key]) && refs[key].indexOf(ref) < 0) { - refs[key].push(ref) - } else { - refs[key] = [ref] + const refs = vnode.context.$refs + + if (typeof key === 'function') { + key(ref) + } else if (vnode.data.refInFor) { + const refArray = refs[key] + if (Array.isArray(refArray)) { + if (refArray.indexOf(ref) < 0) { + refArray.push(ref) } } else { - refs[key] = ref + refs[key] = [ref] + } + } else { + refs[key] = ref + } +} + +export function resetRefs (refs: Refs): Refs { + const res = {} + // keep existing v-for ref arrays even if empty + for (const key in refs) { + if (Array.isArray(refs[key])) { + res[key] = [] } } + return res } diff --git a/src/core/vdom/patch.js b/src/core/vdom/patch.js index aac33dc4..27632aee 100644 --- a/src/core/vdom/patch.js +++ b/src/core/vdom/patch.js @@ -189,7 +189,7 @@ export function createPatchFunction (backend) { } else { // empty component root. // skip all element-related modules except for ref (#3455) - registerRef(vnode) + registerRef(null, vnode) // make sure to invoke the insert hook insertedVnodeQueue.push(vnode) } diff --git a/test/unit/features/ref.spec.js b/test/unit/features/ref.spec.js index da637b7b..1aa6760b 100644 --- a/test/unit/features/ref.spec.js +++ b/test/unit/features/ref.spec.js @@ -157,4 +157,72 @@ describe('ref', () => { }).$mount() expect(vm.$refs.test).toBe(vm.$children[0]) }) + + it('should should call callback method (v-for)', done => { + const vm = new Vue({ + data: { + items: [1, 2, 3] + }, + template: ` +
+ +
+ `, + components: { + test: { + props: ['n'], + template: '
{{ n }}
' + } + }, + methods: { + onRef (ref) { + (this.$refs.list || (this.$refs.list = [])).push(ref) + } + } + }).$mount() + assertRefs() + // updating + vm.items.push(4) + waitForUpdate(assertRefs) + .then(() => { vm.items = [] }) + .then(assertRefs) + .then(done) + + function assertRefs () { + expect(Array.isArray(vm.$refs.list)).toBe(true) + expect(vm.$refs.list.length).toBe(vm.items.length) + expect(vm.$refs.list.every((comp, i) => comp.$el.textContent === String(i + 1))).toBe(true) + } + }) + + it('should should call inline callback (v-for)', done => { + const vm = new Vue({ + data: { + items: [1, 2, 3] + }, + template: ` +
+ +
+ `, + components: { + test: { + props: ['n'], + template: '
{{ n }}
' + } + } + }).$mount() + assertRefs() + // updating + vm.items.push(4) + waitForUpdate(assertRefs) + .then(() => { vm.items = [] }) + .then(assertRefs) + .then(done) + + function assertRefs () { + expect(Object.keys(vm.$refs).length).toBe(vm.items.length) + expect(Object.keys(vm.$refs).every(i => vm.$refs[i].$el.textContent === String(i))).toBe(true) + } + }) })