Przeglądaj źródła

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

fix #6263
赵鑫晖 8 lat temu
rodzic
commit
59dbd4a414

+ 2 - 2
flow/component.js

@@ -29,8 +29,8 @@ declare interface Component {
   $slots: { [key: string]: Array<VNode> };
   $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<Function> };
+  $attrs: { [key: string] : string };
+  $listeners: { [key: string]: Function | Array<Function> };
   $isServer: boolean;
 
   // public methods

+ 2 - 2
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) {

+ 5 - 4
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)
   }
 }
 

+ 14 - 0
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: `<foo/>`,
+      data: { foo: 'foo' },
+      components: {
+        foo: {
+          template: `<div>{{ this.foo }}</div>`
+        }
+      }
+    }).$mount()
+    expect(vm.$attrs).toBeDefined()
+  })
+
   it('warn mutating $attrs', () => {
     const vm = new Vue()
     vm.$attrs = {}

+ 2 - 2
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<Function> } | void;
+  readonly $attrs: { [key: string] : string };
+  readonly $listeners: { [key: string]: Function | Array<Function> };
 
   $mount(elementOrSelector?: Element | String, hydrating?: boolean): this;
   $forceUpdate(): void;