diff --git a/packages/runtime-core/__tests__/componentProps.spec.ts b/packages/runtime-core/__tests__/componentProps.spec.ts
index 885e80090a1..3a16840b4f3 100644
--- a/packages/runtime-core/__tests__/componentProps.spec.ts
+++ b/packages/runtime-core/__tests__/componentProps.spec.ts
@@ -282,6 +282,56 @@ describe('component props', () => {
expect(root.innerHTML).toBe('
2
')
})
+ describe('validator', () => {
+ test('validator should be called with two arguments', async () => {
+ const mockFn = vi.fn((...args: any[]) => true)
+ const Comp = defineComponent({
+ props: {
+ foo: {
+ type: Number,
+ validator: (value, props) => mockFn(value, props)
+ },
+ bar: {
+ type: Number
+ }
+ },
+ template: ``
+ })
+
+ // Note this one is using the main Vue render so it can compile template
+ // on the fly
+ const root = document.createElement('div')
+ domRender(h(Comp, { foo: 1, bar: 2 }), root)
+ expect(mockFn).toHaveBeenCalledWith(1, { foo: 1, bar: 2 })
+ })
+
+ test('validator should not be able to mutate other props', async () => {
+ const mockFn = vi.fn((...args: any[]) => true)
+ const Comp = defineComponent({
+ props: {
+ foo: {
+ type: Number,
+ validator: (value, props) => !!(props.bar = 1)
+ },
+ bar: {
+ type: Number,
+ validator: value => mockFn(value)
+ }
+ },
+ template: ``
+ })
+
+ // Note this one is using the main Vue render so it can compile template
+ // on the fly
+ const root = document.createElement('div')
+ domRender(h(Comp, { foo: 1, bar: 2 }), root)
+ expect(
+ `Set operation on key "bar" failed: target is readonly.`
+ ).toHaveBeenWarnedLast()
+ expect(mockFn).toHaveBeenCalledWith(2)
+ })
+ })
+
test('warn props mutation', () => {
let instance: ComponentInternalInstance
let setupProps: any
diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts
index bbe88bf7b82..4b7be8a8e73 100644
--- a/packages/runtime-core/src/componentProps.ts
+++ b/packages/runtime-core/src/componentProps.ts
@@ -2,7 +2,8 @@ import {
toRaw,
shallowReactive,
trigger,
- TriggerOpTypes
+ TriggerOpTypes,
+ shallowReadonly
} from '@vue/reactivity'
import {
EMPTY_OBJ,
@@ -57,7 +58,7 @@ export interface PropOptions {
type?: PropType | true | null
required?: boolean
default?: D | DefaultFactory | null | undefined | object
- validator?(value: unknown): boolean
+ validator?(value: unknown, props: Data): boolean
/**
* @internal
*/
@@ -634,6 +635,7 @@ function validateProps(
key,
resolvedValues[key],
opt,
+ __DEV__ ? shallowReadonly(resolvedValues) : resolvedValues,
!hasOwn(rawProps, key) && !hasOwn(rawProps, hyphenate(key))
)
}
@@ -646,6 +648,7 @@ function validateProp(
name: string,
value: unknown,
prop: PropOptions,
+ props: Data,
isAbsent: boolean
) {
const { type, required, validator, skipCheck } = prop
@@ -675,7 +678,7 @@ function validateProp(
}
}
// custom validator
- if (validator && !validator(value)) {
+ if (validator && !validator(value, props)) {
warn('Invalid prop: custom validator check failed for prop "' + name + '".')
}
}