lock,
unlock,
effect,
- ref
+ ref,
+ readonlyProps
} from '../src'
import { mockWarn } from '@vue/runtime-test'
`Set operation on key "value" failed: target is readonly.`
).toHaveBeenWarned()
})
+
+ describe('readonlyProps', () => {
+ test('should not unwrap root-level refs', () => {
+ const props = readonlyProps({ n: ref(1) })
+ expect(props.n.value).toBe(1)
+ })
+
+ test('should unwrap nested refs', () => {
+ const props = readonlyProps({ foo: { bar: ref(1) } })
+ expect(props.foo.bar).toBe(1)
+ })
+
+ test('should make properties readonly', () => {
+ const props = readonlyProps({ n: ref(1) })
+ props.n.value = 2
+ expect(props.n.value).toBe(1)
+ expect(
+ `Set operation on key "value" failed: target is readonly.`
+ ).toHaveBeenWarned()
+
+ // @ts-ignore
+ props.n = 2
+ expect(props.n.value).toBe(1)
+ expect(
+ `Set operation on key "n" failed: target is readonly.`
+ ).toHaveBeenWarned()
+ })
+ })
})
.filter(isSymbol)
)
-function createGetter(isReadonly: boolean) {
+function createGetter(isReadonly: boolean, unwrap: boolean = true) {
return function get(target: object, key: string | symbol, receiver: object) {
- const res = Reflect.get(target, key, receiver)
+ let res = Reflect.get(target, key, receiver)
if (isSymbol(key) && builtInSymbols.has(key)) {
return res
}
- if (isRef(res)) {
- return res.value
+ if (unwrap && isRef(res)) {
+ res = res.value
+ } else {
+ track(target, OperationTypes.GET, key)
}
- track(target, OperationTypes.GET, key)
return isObject(res)
? isReadonly
? // need to lazy access readonly and reactive here to avoid
has,
ownKeys
}
+
+// props handlers are special in the sense that it should not unwrap top-level
+// refs (in order to allow refs to be explicitly passed down), but should
+// retain the reactivity of the normal readonly object.
+export const readonlyPropsHandlers: ProxyHandler<object> = {
+ ...readonlyHandlers,
+ get: createGetter(true, false)
+}
isReactive,
readonly,
isReadonly,
+ readonlyProps,
toRaw,
markReadonly,
markNonReactive
import { isObject, toRawType } from '@vue/shared'
-import { mutableHandlers, readonlyHandlers } from './baseHandlers'
+import {
+ mutableHandlers,
+ readonlyHandlers,
+ readonlyPropsHandlers
+} from './baseHandlers'
import {
mutableCollectionHandlers,
readonlyCollectionHandlers
)
}
+// @internal
+// Return a readonly-copy of a props object, without unwrapping refs at the root
+// level. This is intended to allow explicitly passing refs as props.
+// Technically this should use different global cache from readonly(), but
+// since it is only used on internal objects so it's not really necessary.
+export function readonlyProps<T extends object>(
+ target: T
+): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {
+ return createReactiveObject(
+ target,
+ rawToReadonly,
+ readonlyToRaw,
+ readonlyPropsHandlers,
+ readonlyCollectionHandlers
+ )
+}
+
function createReactiveObject(
target: unknown,
toProxy: WeakMap<any, any>,
import { VNode, VNodeChild, isVNode } from './vnode'
-import { ReactiveEffect, reactive, readonly } from '@vue/reactivity'
+import { ReactiveEffect, reactive, readonlyProps } from '@vue/reactivity'
import {
PublicInstanceProxyHandlers,
ComponentPublicInstance
// 2. create props proxy
// the propsProxy is a reactive AND readonly proxy to the actual props.
// it will be updated in resolveProps() on updates before render
- const propsProxy = (instance.propsProxy = readonly(instance.props))
+ const propsProxy = (instance.propsProxy = readonlyProps(instance.props))
// 3. call setup()
const { setup } = Component
if (setup) {
-import { readonly, toRaw, lock, unlock } from '@vue/reactivity'
+import { toRaw, lock, unlock } from '@vue/reactivity'
import {
EMPTY_OBJ,
camelize,
// lock readonly
lock()
- instance.props = __DEV__ ? readonly(props) : props
- instance.attrs = options
- ? __DEV__ && attrs != null
- ? readonly(attrs)
- : attrs || EMPTY_OBJ
- : instance.props
+ instance.props = props
+ instance.attrs = options ? attrs || EMPTY_OBJ : props
}
const normalizationMap = new WeakMap()
import { handleError, ErrorCodes } from './errorHandling'
import { PatchFlags, EMPTY_OBJ } from '@vue/shared'
import { warn } from './warning'
+import { readonlyProps } from '@vue/reactivity'
// mark the current rendering instance for asset resolution (e.g.
// resolveComponent, resolveDirective) during render
} else {
// functional
const render = Component as FunctionalComponent
+ const propsToPass = __DEV__ ? readonlyProps(props) : props
result = normalizeVNode(
render.length > 1
- ? render(props, {
+ ? render(propsToPass, {
attrs,
slots,
emit
})
- : render(props, null as any /* we know it doesn't need it */)
+ : render(propsToPass, null as any /* we know it doesn't need it */)
)
}