diff --git a/packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts b/packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts index 1de05b67b44..eceb7d54609 100644 --- a/packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts +++ b/packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts @@ -729,4 +729,39 @@ describe('attribute fallthrough', () => { expect(textBar).toBe('from GrandChild') expect(textFoo).toBe('from Child') }) + + // #9039 + it('should not fallthrough attr if it has been declared as a prop in the root component', () => { + const App = defineComponent({ + setup() { + return () => + h(Child, { + foo: '123' + }) + } + }) + + const Child = defineComponent({ + setup(_props) { + const foo = ref('456') + return () => + h(GrandChild, { + foo: foo.value + }) + } + }) + const GrandChild = defineComponent({ + props: ['foo'], + setup(_props) { + return () => h('span', null, _props.foo) + } + }) + + const root = document.createElement('div') + document.body.appendChild(root) + render(h(App), root) + + const node = root.children[0] as HTMLElement + expect(node.innerHTML).toBe('456') + }) }) diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index d9de968a074..90ba1835ca0 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -2,7 +2,8 @@ import { ComponentInternalInstance, FunctionalComponent, Data, - getComponentName + getComponentName, + ConcreteComponent } from './component' import { VNode, @@ -15,7 +16,14 @@ import { blockStack } from './vnode' import { handleError, ErrorCodes } from './errorHandling' -import { PatchFlags, ShapeFlags, isOn, isModelListener } from '@vue/shared' +import { + PatchFlags, + ShapeFlags, + isOn, + isModelListener, + isObject, + isArray +} from '@vue/shared' import { warn } from './warning' import { isHmrUpdating } from './hmr' import { NormalizedProps } from './componentProps' @@ -133,8 +141,23 @@ export function renderComponentRoot( } if (fallthroughAttrs && inheritAttrs !== false) { + const { shapeFlag, type, props } = root + // fix #8969 should not fallthrough attr if it has been declared as a prop in the root component + if (shapeFlag & ShapeFlags.COMPONENT && props) { + Object.keys(fallthroughAttrs).forEach(key => { + if (key in props) { + const propsDef = (type as ConcreteComponent).props + if ( + propsDef && + ((isObject(propsDef) && key in propsDef) || + (isArray(propsDef) && propsDef.includes(key))) + ) + delete fallthroughAttrs![key] + } + }) + } + const keys = Object.keys(fallthroughAttrs) - const { shapeFlag } = root if (keys.length) { if (shapeFlag & (ShapeFlags.ELEMENT | ShapeFlags.COMPONENT)) { if (propsOptions && keys.some(isModelListener)) {