diff --git a/packages/runtime-vapor/__tests__/componentProps.spec.ts b/packages/runtime-vapor/__tests__/componentProps.spec.ts
index c068e8044cb..55b492408c0 100644
--- a/packages/runtime-vapor/__tests__/componentProps.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentProps.spec.ts
@@ -497,6 +497,36 @@ describe('component: props', () => {
expect(changeSpy).toHaveBeenCalledTimes(1)
})
+ test('should not warn invalid watch source when directly watching props', async () => {
+ const changeSpy = vi.fn()
+ const { render, html } = define({
+ props: {
+ foo: {
+ type: String,
+ },
+ },
+ setup(props: any) {
+ watch(props, changeSpy)
+ const t0 = template('
')
+ const n0 = t0()
+ renderEffect(() => {
+ setElementText(n0, String(props.foo))
+ })
+ return n0
+ },
+ })
+
+ const foo = ref('foo')
+ render({ foo: () => foo.value })
+ expect(html()).toBe(`foo
`)
+ expect('Invalid watch source').not.toHaveBeenWarned()
+
+ foo.value = 'bar'
+ await nextTick()
+ expect(html()).toBe(`bar
`)
+ expect(changeSpy).toHaveBeenCalledTimes(1)
+ })
+
test('support null in required + multiple-type declarations', () => {
const { render } = define({
props: {
diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts
index a5e9daad229..6de001ffb92 100644
--- a/packages/runtime-vapor/src/componentProps.ts
+++ b/packages/runtime-vapor/src/componentProps.ts
@@ -21,6 +21,7 @@ import {
validateProps,
warn,
} from '@vue/runtime-dom'
+import { ReactiveFlags } from '@vue/reactivity'
import { normalizeEmitsOptions } from './componentEmits'
import { renderEffect } from './renderEffect'
@@ -63,6 +64,9 @@ export function getPropsProxyHandlers(
: YES
const getProp = (instance: VaporComponentInstance, key: string | symbol) => {
+ // this enables direct watching of props and prevents `Invalid watch source` DEV warnings.
+ if (key === ReactiveFlags.IS_REACTIVE) return true
+
if (!isProp(key)) return
const rawProps = instance.rawProps
const dynamicSources = rawProps.$