Skip to content

Commit 149821a

Browse files
authored
feat: proxyRefs method and ShallowUnwrapRefs type (#456)
* feat: `proxyRefs` method and `ShallowUnwrapRefs` type BREAKING CHANGE: template auto ref unwrapping are now applied shallowly, i.e. only at the root level. See vuejs/core#1682 for more details.
1 parent 95d87f1 commit 149821a

File tree

9 files changed

+146
-141
lines changed

9 files changed

+146
-141
lines changed

README.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ watch(() => {
361361

362362
</details>
363363

364-
### createApp
364+
### `createApp`
365365

366366
<details>
367367
<summary>
@@ -381,7 +381,7 @@ app2.component('Bar', Bar) // equivalent to Vue.use('Bar', Bar)
381381

382382
</details>
383383

384-
### shallowReadonly
384+
### `shallowReadonly`
385385

386386
<details>
387387
<summary>
@@ -392,6 +392,29 @@ app2.component('Bar', Bar) // equivalent to Vue.use('Bar', Bar)
392392
393393
</details>
394394

395+
### `props`
396+
<details>
397+
<summary>
398+
⚠️ <code>toRefs(props.foo.bar)</code> will incorrectly warn when acessing nested levels of props.
399+
⚠️ <code>isReactive(props.foo.bar)</code> will return false.
400+
</summary>
401+
402+
```ts
403+
defineComponent({
404+
setup(props) {
405+
const { bar } = toRefs(props.foo) // it will `warn`
406+
407+
// use this instead
408+
const { foo } = toRefs(props)
409+
const a = foo.value.bar
410+
}
411+
})
412+
```
413+
414+
</details>
415+
416+
417+
395418
### Missing APIs
396419

397420
The following APIs introduced in Vue 3 are not available in this plugin.

src/apis/state.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ export {
1818
UnwrapRef,
1919
isReadonly,
2020
shallowReadonly,
21+
proxyRefs,
22+
ShallowUnwrapRef,
2123
} from '../reactivity'

src/component/componentProxy.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ExtractPropTypes } from './componentProps'
2-
import { UnwrapRef } from '..'
2+
import { ShallowUnwrapRef } from '..'
33
import { Data } from './common'
44

55
import Vue, {
@@ -28,7 +28,7 @@ export type ComponentRenderProxy<
2828
$props: Readonly<P & PublicProps>
2929
$attrs: Data
3030
} & Readonly<P> &
31-
UnwrapRef<B> &
31+
ShallowUnwrapRef<B> &
3232
D &
3333
M &
3434
ExtractComputedReturns<C> &
@@ -38,7 +38,7 @@ export type ComponentRenderProxy<
3838
type VueConstructorProxy<PropsOptions, RawBindings> = VueConstructor & {
3939
new (...args: any[]): ComponentRenderProxy<
4040
ExtractPropTypes<PropsOptions>,
41-
UnwrapRef<RawBindings>,
41+
ShallowUnwrapRef<RawBindings>,
4242
ExtractPropTypes<PropsOptions, false>
4343
>
4444
}
@@ -55,7 +55,7 @@ export type VueProxy<
5555
Methods = DefaultMethods<Vue>
5656
> = Vue2ComponentOptions<
5757
Vue,
58-
UnwrapRef<RawBindings> & Data,
58+
ShallowUnwrapRef<RawBindings> & Data,
5959
Methods,
6060
Computed,
6161
PropsOptions,

src/mixin.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,7 @@ import {
55
SetupFunction,
66
Data,
77
} from './component'
8-
import {
9-
isRef,
10-
isReactive,
11-
markRaw,
12-
unwrapRefProxy,
13-
markReactive,
14-
} from './reactivity'
8+
import { isRef, isReactive, markRaw, markReactive } from './reactivity'
159
import { isPlainObject, assert, proxy, warn, isFunction } from './utils'
1610
import { ref } from './apis'
1711
import vmStateManager from './utils/vmStateManager'
@@ -79,7 +73,7 @@ export function mixin(Vue: VueConstructor) {
7973
const setup = vm.$options.setup!
8074
const ctx = createSetupContext(vm)
8175

82-
// mark props as reactive
76+
// mark props
8377
markReactive(props)
8478

8579
// resolve scopedSlots and slots to functions
@@ -116,12 +110,8 @@ export function mixin(Vue: VueConstructor) {
116110
if (isFunction(bindingValue)) {
117111
bindingValue = bindingValue.bind(vm)
118112
}
119-
// unwrap all ref properties
120-
const unwrapped = unwrapRefProxy(bindingValue)
121-
// mark the object as reactive
122-
markReactive(unwrapped)
123113
// a non-reactive should not don't get reactivity
124-
bindingValue = ref(markRaw(unwrapped))
114+
bindingValue = ref(markRaw(bindingValue))
125115
}
126116
}
127117
asVmProperty(vm, name, bindingValue)

src/reactivity/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export {
2121
unref,
2222
shallowRef,
2323
triggerRef,
24+
proxyRefs,
25+
ShallowUnwrapRef,
2426
} from './ref'
2527
export { set } from './set'
26-
export { unwrapRefProxy } from './unwrap'

src/reactivity/ref.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { Data } from '../component'
22
import { RefKey, ReadonlyIdentifierKey } from '../utils/symbols'
33
import { proxy, isPlainObject, warn } from '../utils'
44
import { reactive, isReactive, shallowReactive } from './reactive'
5-
import { ComputedRef } from '../apis/computed'
65

76
declare const _refBrand: unique symbol
87
export interface Ref<T = any> {
@@ -22,16 +21,18 @@ type WeakCollections = WeakMap<any, any> | WeakSet<any>
2221
// RelativePath extends object -> true
2322
type BaseTypes = string | number | boolean | Node | Window
2423

25-
export type UnwrapRef<T> = T extends ComputedRef<infer V>
26-
? UnwrapRefSimple<V>
27-
: T extends Ref<infer V>
24+
export type ShallowUnwrapRef<T> = {
25+
[K in keyof T]: T[K] extends Ref<infer V> ? V : T[K]
26+
}
27+
28+
export type UnwrapRef<T> = T extends Ref<infer V>
2829
? UnwrapRefSimple<V>
2930
: UnwrapRefSimple<T>
3031

3132
type UnwrapRefSimple<T> = T extends Function | CollectionTypes | BaseTypes | Ref
3233
? T
3334
: T extends Array<any>
34-
? T
35+
? { [K in keyof T]: UnwrapRefSimple<T[K]> }
3536
: T extends object
3637
? UnwrappedObject<T>
3738
: T
@@ -50,6 +51,7 @@ type SymbolExtract<T> = (T extends { [Symbol.asyncIterator]: infer V }
5051
: {}) &
5152
(T extends { [Symbol.iterator]: infer V } ? { [Symbol.iterator]: V } : {}) &
5253
(T extends { [Symbol.match]: infer V } ? { [Symbol.match]: V } : {}) &
54+
(T extends { [Symbol.matchAll]: infer V } ? { [Symbol.matchAll]: V } : {}) &
5355
(T extends { [Symbol.replace]: infer V } ? { [Symbol.replace]: V } : {}) &
5456
(T extends { [Symbol.search]: infer V } ? { [Symbol.search]: V } : {}) &
5557
(T extends { [Symbol.species]: infer V } ? { [Symbol.species]: V } : {}) &
@@ -188,3 +190,31 @@ export function triggerRef(value: any) {
188190

189191
value.value = value.value
190192
}
193+
194+
export function proxyRefs<T extends object>(
195+
objectWithRefs: T
196+
): ShallowUnwrapRef<T> {
197+
if (isReactive(objectWithRefs)) {
198+
return objectWithRefs as ShallowUnwrapRef<T>
199+
}
200+
const value: Record<string, any> = reactive({ [RefKey]: objectWithRefs })
201+
202+
for (const key of Object.keys(objectWithRefs)) {
203+
proxy(value, key, {
204+
get() {
205+
if (isRef(value[key])) {
206+
return value[key].value
207+
}
208+
return value[key]
209+
},
210+
set(v: unknown) {
211+
if (isRef(value[key])) {
212+
return (value[key].value = unref(v))
213+
}
214+
value[key] = unref(v)
215+
},
216+
})
217+
}
218+
219+
return value as ShallowUnwrapRef<T>
220+
}

src/reactivity/unwrap.ts

Lines changed: 0 additions & 56 deletions
This file was deleted.

test-dts/defineComponent.test-d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ describe('with object props', () => {
155155

156156
// assert setup context unwrapping
157157
expectType<number>(this.c)
158-
expectType<string>(this.d.e)
158+
expectType<string>(this.d.e.value)
159159
expectType<GT>(this.f.g)
160160

161161
// setup context properties should be mutable

0 commit comments

Comments
 (0)