Browse Source

fix: ensure $attrs and $listeners are always objects (#6441)

fix #6263
dev
赵鑫晖 7 years ago
committed by Evan You
parent
commit
59dbd4a414
  1. 4
      flow/component.js
  2. 4
      src/core/instance/lifecycle.js
  3. 9
      src/core/instance/render.js
  4. 14
      test/unit/features/instance/properties.spec.js
  5. 4
      types/vue.d.ts

4
flow/component.js

@ -29,8 +29,8 @@ declare interface Component {
$slots: { [key: string]: Array<VNode> }; $slots: { [key: string]: Array<VNode> };
$scopedSlots: { [key: string]: () => VNodeChildren }; $scopedSlots: { [key: string]: () => VNodeChildren };
$vnode: VNode; // the placeholder node for the component in parent's render tree $vnode: VNode; // the placeholder node for the component in parent's render tree
$attrs: ?{ [key: string] : string }; $attrs: { [key: string] : string };
$listeners: ?{ [key: string]: Function | Array<Function> }; $listeners: { [key: string]: Function | Array<Function> };
$isServer: boolean; $isServer: boolean;
// public methods // public methods

4
src/core/instance/lifecycle.js

@ -232,8 +232,8 @@ export function updateChildComponent (
// update $attrs and $listensers hash // update $attrs and $listensers hash
// these are also reactive so they may trigger child update if the child // these are also reactive so they may trigger child update if the child
// used them during render // used them during render
vm.$attrs = parentVnode.data && parentVnode.data.attrs vm.$attrs = (parentVnode.data && parentVnode.data.attrs) || emptyObject
vm.$listeners = listeners vm.$listeners = listeners || emptyObject
// update props // update props
if (propsData && vm.$options.props) { if (propsData && vm.$options.props) {

9
src/core/instance/render.js

@ -49,17 +49,18 @@ export function initRender (vm: Component) {
// $attrs & $listeners are exposed for easier HOC creation. // $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated // they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */ /* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') { 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) !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true) }, true)
defineReactive(vm, '$listeners', vm.$options._parentListeners, () => { defineReactive(vm, '$listeners', vm.$options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm) !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true) }, true)
} else { } else {
defineReactive(vm, '$attrs', parentData && parentData.attrs, null, true) defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', vm.$options._parentListeners, null, true) defineReactive(vm, '$listeners', vm.$options._parentListeners || emptyObject, null, true)
} }
} }

14
test/unit/features/instance/properties.spec.js

@ -146,6 +146,20 @@ describe('Instance properties', () => {
}).then(done) }).then(done)
}) })
// #6263
it('$attrs should not be undefined when no props passed in', () => {
const vm = new Vue({
template: `<foo/>`,
data: { foo: 'foo' },
components: {
foo: {
template: `<div>{{ this.foo }}</div>`
}
}
}).$mount()
expect(vm.$attrs).toBeDefined()
})
it('warn mutating $attrs', () => { it('warn mutating $attrs', () => {
const vm = new Vue() const vm = new Vue()
vm.$attrs = {} vm.$attrs = {}

4
types/vue.d.ts

@ -45,8 +45,8 @@ export declare class Vue {
readonly $ssrContext: any; readonly $ssrContext: any;
readonly $props: any; readonly $props: any;
readonly $vnode: VNode; readonly $vnode: VNode;
readonly $attrs: { [key: string] : string } | void; readonly $attrs: { [key: string] : string };
readonly $listeners: { [key: string]: Function | Array<Function> } | void; readonly $listeners: { [key: string]: Function | Array<Function> };
$mount(elementOrSelector?: Element | String, hydrating?: boolean): this; $mount(elementOrSelector?: Element | String, hydrating?: boolean): this;
$forceUpdate(): void; $forceUpdate(): void;

Loading…
Cancel
Save