Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(reactivity): proxyRefs method and ShallowUnwrapRefs type #1682

Merged
merged 3 commits into from
Jul 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/reactivity/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
export {
ref,
unref,
shallowRef,
isRef,
toRef,
toRefs,
unref,
proxyRefs,
customRef,
triggerRef,
Ref,
UnwrapRef,
ToRefs,
UnwrapRef,
ShallowUnwrapRef,
RefUnwrapBailTypes
} from './ref'
export {
Expand Down
27 changes: 26 additions & 1 deletion packages/reactivity/src/ref.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -81,6 +81,27 @@ export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
return isRef(ref) ? (ref.value as any) : ref
}

const shallowUnwrapHandlers: ProxyHandler<any> = {
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<T extends object>(
objectWithRefs: T
): ShallowUnwrapRef<T> {
return isReactive(objectWithRefs)
? objectWithRefs
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
}

export type CustomRefFactory<T> = (
track: () => void,
trigger: () => void
Expand Down Expand Up @@ -156,6 +177,10 @@ type BaseTypes = string | number | boolean
*/
export interface RefUnwrapBailTypes {}

export type ShallowUnwrapRef<T> = {
[K in keyof T]: T[K] extends Ref<infer V> ? V : T[K]
}
Comment on lines +180 to +182
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this is better ?

export type ShallowUnwrapRef<T> = T extends ReturnType<typeof reactive>
  ? T
  : { [K in keyof T]: T[K] extends Ref<infer V> ? V : T[K] }


export type UnwrapRef<T> = T extends Ref<infer V>
? UnwrapRefSimple<V>
: UnwrapRefSimple<T>
Expand Down
6 changes: 3 additions & 3 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { VNode, VNodeChild, isVNode } from './vnode'
import {
reactive,
ReactiveEffect,
pauseTracking,
resetTracking,
shallowReadonly
shallowReadonly,
proxyRefs
} from '@vue/reactivity'
import {
CreateComponentPublicInstance,
Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yyx990803 is it on purpose that proxyRefs doesn't check if its argument is reactive and always wraps it with a new proxy?
I very often return state that is reactive({}) and it seems wasteful to always go 2 layers deep in proxies to access state, when just one would be perfectly fine.

I'd rather have proxyRefs be a default fallback when I pass a plain object.

Or going full circle to one of my very first suggestions: not wrapping at all and leaving the responsibility of picking a wrapper to setup. (it seems reasonable that <script setup> always wraps the exports in a proxyRefs).

if (__DEV__) {
exposeSetupStateOnRenderContext(instance)
}
Expand Down
6 changes: 3 additions & 3 deletions packages/runtime-core/src/componentProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import {
} from '@vue/shared'
import {
ReactiveEffect,
UnwrapRef,
toRaw,
shallowReadonly,
ReactiveFlags,
track,
TrackOpTypes
TrackOpTypes,
ShallowUnwrapRef
} from '@vue/reactivity'
import {
ExtractComputedReturns,
Expand Down Expand Up @@ -154,7 +154,7 @@ export type ComponentPublicInstance<
$nextTick: typeof nextTick
$watch: typeof instanceWatch
} & P &
UnwrapRef<B> &
ShallowUnwrapRef<B> &
D &
ExtractComputedReturns<C> &
M &
Expand Down
2 changes: 2 additions & 0 deletions packages/runtime-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {
readonly,
// utilities
unref,
proxyRefs,
isRef,
toRef,
toRefs,
Expand Down Expand Up @@ -125,6 +126,7 @@ export {
ComputedRef,
WritableComputedRef,
UnwrapRef,
ShallowUnwrapRef,
WritableComputedOptions,
ToRefs,
DeepReadonly
Expand Down
2 changes: 1 addition & 1 deletion test-dts/defineComponent.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ describe('with object props', () => {

// assert setup context unwrapping
expectType<number>(this.c)
expectType<string>(this.d.e)
expectType<string>(this.d.e.value)
expectType<GT>(this.f.g)

// setup context properties should be mutable
Expand Down