From 59dbd4a414394a3ce581f9fbd9554da9af9e4b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E9=91=AB=E6=99=96?= <120797887@qq.com> Date: Wed, 30 Aug 2017 04:59:39 +0800 Subject: [PATCH] fix: ensure $attrs and $listeners are always objects (#6441) fix #6263 --- flow/component.js | 4 ++-- src/core/instance/lifecycle.js | 4 ++-- src/core/instance/render.js | 9 +++++---- test/unit/features/instance/properties.spec.js | 14 ++++++++++++++ types/vue.d.ts | 4 ++-- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/flow/component.js b/flow/component.js index 08fcfd4a..381943ed 100644 --- a/flow/component.js +++ b/flow/component.js @@ -29,8 +29,8 @@ declare interface Component { $slots: { [key: string]: Array }; $scopedSlots: { [key: string]: () => VNodeChildren }; $vnode: VNode; // the placeholder node for the component in parent's render tree - $attrs: ?{ [key: string] : string }; - $listeners: ?{ [key: string]: Function | Array }; + $attrs: { [key: string] : string }; + $listeners: { [key: string]: Function | Array }; $isServer: boolean; // public methods diff --git a/src/core/instance/lifecycle.js b/src/core/instance/lifecycle.js index fd2c9e5b..2f923c07 100644 --- a/src/core/instance/lifecycle.js +++ b/src/core/instance/lifecycle.js @@ -232,8 +232,8 @@ export function updateChildComponent ( // update $attrs and $listensers hash // these are also reactive so they may trigger child update if the child // used them during render - vm.$attrs = parentVnode.data && parentVnode.data.attrs - vm.$listeners = listeners + vm.$attrs = (parentVnode.data && parentVnode.data.attrs) || emptyObject + vm.$listeners = listeners || emptyObject // update props if (propsData && vm.$options.props) { diff --git a/src/core/instance/render.js b/src/core/instance/render.js index cf2e8fe8..62b48781 100644 --- a/src/core/instance/render.js +++ b/src/core/instance/render.js @@ -49,17 +49,18 @@ export function initRender (vm: Component) { // $attrs & $listeners are exposed for easier HOC creation. // they need to be reactive so that HOCs using them are always updated const parentData = parentVnode && parentVnode.data + /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { - defineReactive(vm, '$attrs', parentData && parentData.attrs, () => { + defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => { !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm) }, true) - defineReactive(vm, '$listeners', vm.$options._parentListeners, () => { + defineReactive(vm, '$listeners', vm.$options._parentListeners || emptyObject, () => { !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm) }, true) } else { - defineReactive(vm, '$attrs', parentData && parentData.attrs, null, true) - defineReactive(vm, '$listeners', vm.$options._parentListeners, null, true) + defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true) + defineReactive(vm, '$listeners', vm.$options._parentListeners || emptyObject, null, true) } } diff --git a/test/unit/features/instance/properties.spec.js b/test/unit/features/instance/properties.spec.js index f14aa52a..02aca12f 100644 --- a/test/unit/features/instance/properties.spec.js +++ b/test/unit/features/instance/properties.spec.js @@ -146,6 +146,20 @@ describe('Instance properties', () => { }).then(done) }) + // #6263 + it('$attrs should not be undefined when no props passed in', () => { + const vm = new Vue({ + template: ``, + data: { foo: 'foo' }, + components: { + foo: { + template: `
{{ this.foo }}
` + } + } + }).$mount() + expect(vm.$attrs).toBeDefined() + }) + it('warn mutating $attrs', () => { const vm = new Vue() vm.$attrs = {} diff --git a/types/vue.d.ts b/types/vue.d.ts index 0bfce6ae..111f5fe9 100644 --- a/types/vue.d.ts +++ b/types/vue.d.ts @@ -45,8 +45,8 @@ export declare class Vue { readonly $ssrContext: any; readonly $props: any; readonly $vnode: VNode; - readonly $attrs: { [key: string] : string } | void; - readonly $listeners: { [key: string]: Function | Array } | void; + readonly $attrs: { [key: string] : string }; + readonly $listeners: { [key: string]: Function | Array }; $mount(elementOrSelector?: Element | String, hydrating?: boolean): this; $forceUpdate(): void;