From 3d98df5eea5b4301b4cedd379360a04f92b2ba96 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 22 Jul 2020 15:37:57 -0400 Subject: [PATCH 1/3] feat(reactivity): `proxyRefs` method and `ShallowUnwrapRefs` type BREAKING CHANGE: template auto ref unwrapping are now applied shallowly, i.e. only at the root level. This change aims to ensure that non-ref values referenced in templates retain the same identity with the value declared inside `setup()` regardless of whether it's wrapped with `reactive` or `readonly`. The breaking case is that given the following: ```js setup() { return { one: ref(1), two: { three: ref(3) }, four: 4 } } ``` After this change, `{{ one }}` in the template will still render `1`, but `{{ two.three }}` will no longer be auto unwrapped as `3`. A common case where this could happen is returning an object of refs from a composition function. The recommendation is to either expose the refs directly, or call the newly exposed `proxyRefs` on the object so that the object's refs are unwrapped like root level refs. In fact, `proxyRefs` is also the method used on the `setup()` return object. This also makes non-ref properties in the returned objects non-reactive, so mutating `four` from the template will no longer trigger updates. --- packages/reactivity/src/index.ts | 6 ++++-- packages/reactivity/src/ref.ts | 23 +++++++++++++++++++++ packages/runtime-core/src/component.ts | 6 +++--- packages/runtime-core/src/componentProxy.ts | 6 +++--- packages/runtime-core/src/index.ts | 2 ++ 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index aaf53242592..b03e916d14a 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -1,15 +1,17 @@ export { ref, - unref, shallowRef, isRef, toRef, toRefs, + unref, + proxyRefs, customRef, triggerRef, Ref, - UnwrapRef, ToRefs, + UnwrapRef, + ShallowUnwrapRef, RefUnwrapBailTypes } from './ref' export { diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index cf217c3db39..96cca86bd61 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -81,6 +81,25 @@ export function unref(ref: T): T extends Ref ? V : T { return isRef(ref) ? (ref.value as any) : ref } +const shallowUnwrapHandlers: ProxyHandler = { + get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)), + set: (target, key, value, receiver) => { + const oldValue = target[key] + if (isRef(oldValue) && !isRef(value)) { + oldValue.value = value + return true + } else { + return Reflect.set(target, key, value, receiver) + } + } +} + +export function proxyRefs( + objectWithRefs: T +): ShallowUnwrapRef { + return new Proxy(objectWithRefs, shallowUnwrapHandlers) +} + export type CustomRefFactory = ( track: () => void, trigger: () => void @@ -156,6 +175,10 @@ type BaseTypes = string | number | boolean */ export interface RefUnwrapBailTypes {} +export type ShallowUnwrapRef = { + [K in keyof T]: T[K] extends Ref ? V : T[K] +} + export type UnwrapRef = T extends Ref ? UnwrapRefSimple : UnwrapRefSimple diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index bb1e8efdb4a..dc0bfd7cd49 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -1,10 +1,10 @@ import { VNode, VNodeChild, isVNode } from './vnode' import { - reactive, ReactiveEffect, pauseTracking, resetTracking, - shallowReadonly + shallowReadonly, + proxyRefs } from '@vue/reactivity' import { CreateComponentPublicInstance, @@ -561,7 +561,7 @@ export function handleSetupResult( } // setup returned bindings. // assuming a render function compiled from template is present. - instance.setupState = reactive(setupResult) + instance.setupState = proxyRefs(setupResult) if (__DEV__) { exposeSetupStateOnRenderContext(instance) } diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts index d2b78318ef7..e043d93eb4b 100644 --- a/packages/runtime-core/src/componentProxy.ts +++ b/packages/runtime-core/src/componentProxy.ts @@ -10,12 +10,12 @@ import { } from '@vue/shared' import { ReactiveEffect, - UnwrapRef, toRaw, shallowReadonly, ReactiveFlags, track, - TrackOpTypes + TrackOpTypes, + ShallowUnwrapRef } from '@vue/reactivity' import { ExtractComputedReturns, @@ -154,7 +154,7 @@ export type ComponentPublicInstance< $nextTick: typeof nextTick $watch: typeof instanceWatch } & P & - UnwrapRef & + ShallowUnwrapRef & D & ExtractComputedReturns & M & diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 757a21382bc..14283b5d46b 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -8,6 +8,7 @@ export { readonly, // utilities unref, + proxyRefs, isRef, toRef, toRefs, @@ -125,6 +126,7 @@ export { ComputedRef, WritableComputedRef, UnwrapRef, + ShallowUnwrapRef, WritableComputedOptions, ToRefs, DeepReadonly From f7b8cf38b41c0049355783da964ea500e9ca730c Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 22 Jul 2020 16:33:40 -0400 Subject: [PATCH 2/3] test: fix dts test --- test-dts/defineComponent.test-d.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-dts/defineComponent.test-d.tsx b/test-dts/defineComponent.test-d.tsx index f37c0eb1eaa..5a4d7af3ce3 100644 --- a/test-dts/defineComponent.test-d.tsx +++ b/test-dts/defineComponent.test-d.tsx @@ -146,7 +146,7 @@ describe('with object props', () => { // assert setup context unwrapping expectType(this.c) - expectType(this.d.e) + expectType(this.d.e.value) expectType(this.f.g) // setup context properties should be mutable From 4b522d312a073bbc669cbf5b2235b1599c81b2c4 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 22 Jul 2020 21:08:39 -0400 Subject: [PATCH 3/3] perf: avoid proxying already reactive proxies --- packages/reactivity/src/ref.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 96cca86bd61..58f7997763b 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -1,7 +1,7 @@ import { track, trigger } from './effect' import { TrackOpTypes, TriggerOpTypes } from './operations' import { isObject, hasChanged } from '@vue/shared' -import { reactive, isProxy, toRaw } from './reactive' +import { reactive, isProxy, toRaw, isReactive } from './reactive' import { CollectionTypes } from './collectionHandlers' declare const RefSymbol: unique symbol @@ -97,7 +97,9 @@ const shallowUnwrapHandlers: ProxyHandler = { export function proxyRefs( objectWithRefs: T ): ShallowUnwrapRef { - return new Proxy(objectWithRefs, shallowUnwrapHandlers) + return isReactive(objectWithRefs) + ? objectWithRefs + : new Proxy(objectWithRefs, shallowUnwrapHandlers) } export type CustomRefFactory = (