refactor(runtime-core): revert setup() result reactive conversion

BREAKING CHANGE: revert setup() result reactive conversion

    Revert 6b10f0c & a840e7d. The motivation of the original change was
    avoiding unnecessary deep conversions, but that can be achieved by
    explicitly marking values non-reactive via `markNonReactive`.

    Removing the reactive conversion behavior leads to an usability
    issue in that plain objects containing refs (which is what most
    composition functions will return), when exposed as a nested
    property from `setup()`, will not unwrap the refs in templates. This
    goes against the "no .value in template" intuition and the only
    workaround requires users to manually wrap it again with `reactive()`.

    So in this commit we are reverting to the previous behavior where
    objects returned from `setup()` are implicitly wrapped with
    `reactive()` for deep ref unwrapping.
This commit is contained in:
Evan You 2020-02-26 19:01:42 -05:00
parent 11d2fb2594
commit e67f655b26
3 changed files with 11 additions and 46 deletions

View File

@ -1,5 +1,6 @@
import { VNode, VNodeChild, isVNode } from './vnode' import { VNode, VNodeChild, isVNode } from './vnode'
import { import {
reactive,
ReactiveEffect, ReactiveEffect,
shallowReadonly, shallowReadonly,
pauseTracking, pauseTracking,
@ -398,7 +399,7 @@ export function handleSetupResult(
} }
// setup returned bindings. // setup returned bindings.
// assuming a render function compiled from template is present. // assuming a render function compiled from template is present.
instance.renderContext = setupResult instance.renderContext = reactive(setupResult)
} else if (__DEV__ && setupResult !== undefined) { } else if (__DEV__ && setupResult !== undefined) {
warn( warn(
`setup() should return an object. Received: ${ `setup() should return an object. Received: ${
@ -474,10 +475,6 @@ function finishComponentSetup(
currentInstance = null currentInstance = null
currentSuspense = null currentSuspense = null
} }
if (instance.renderContext === EMPTY_OBJ) {
instance.renderContext = {}
}
} }
// used to identify a setup context proxy // used to identify a setup context proxy

View File

@ -8,14 +8,7 @@ import {
ComputedOptions, ComputedOptions,
MethodOptions MethodOptions
} from './apiOptions' } from './apiOptions'
import { import { ReactiveEffect, UnwrapRef } from '@vue/reactivity'
ReactiveEffect,
isRef,
isReactive,
Ref,
ComputedRef,
unref
} from '@vue/reactivity'
import { warn } from './warning' import { warn } from './warning'
import { Slots } from './componentSlots' import { Slots } from './componentSlots'
import { import {
@ -48,17 +41,11 @@ export type ComponentPublicInstance<
$nextTick: typeof nextTick $nextTick: typeof nextTick
$watch: typeof instanceWatch $watch: typeof instanceWatch
} & P & } & P &
UnwrapSetupBindings<B> & UnwrapRef<B> &
D & D &
ExtractComputedReturns<C> & ExtractComputedReturns<C> &
M M
type UnwrapSetupBindings<B> = { [K in keyof B]: UnwrapBinding<B[K]> }
type UnwrapBinding<B> = B extends ComputedRef<any>
? B extends ComputedRef<infer V> ? V : B
: B extends Ref<infer V> ? V : B
const publicPropertiesMap: Record< const publicPropertiesMap: Record<
string, string,
(i: ComponentInternalInstance) => any (i: ComponentInternalInstance) => any
@ -115,7 +102,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
case AccessTypes.DATA: case AccessTypes.DATA:
return data[key] return data[key]
case AccessTypes.CONTEXT: case AccessTypes.CONTEXT:
return unref(renderContext[key]) return renderContext[key]
case AccessTypes.PROPS: case AccessTypes.PROPS:
return propsProxy![key] return propsProxy![key]
// default: just fallthrough // default: just fallthrough
@ -123,9 +110,9 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) { } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
accessCache![key] = AccessTypes.DATA accessCache![key] = AccessTypes.DATA
return data[key] return data[key]
} else if (hasOwn(renderContext, key)) { } else if (renderContext !== EMPTY_OBJ && hasOwn(renderContext, key)) {
accessCache![key] = AccessTypes.CONTEXT accessCache![key] = AccessTypes.CONTEXT
return unref(renderContext[key]) return renderContext[key]
} else if (type.props != null) { } else if (type.props != null) {
// only cache other properties when instance has declared (this stable) // only cache other properties when instance has declared (this stable)
// props // props
@ -180,19 +167,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
if (data !== EMPTY_OBJ && hasOwn(data, key)) { if (data !== EMPTY_OBJ && hasOwn(data, key)) {
data[key] = value data[key] = value
} else if (hasOwn(renderContext, key)) { } else if (hasOwn(renderContext, key)) {
// context is already reactive (user returned reactive object from setup()) renderContext[key] = value
// just set directly
if (isReactive(renderContext)) {
renderContext[key] = value
} else {
// handle potential ref set
const oldValue = renderContext[key]
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value
} else {
renderContext[key] = value
}
}
} else if (key[0] === '$' && key.slice(1) in target) { } else if (key[0] === '$' && key.slice(1) in target) {
__DEV__ && __DEV__ &&
warn( warn(

View File

@ -4,7 +4,6 @@ import {
defineComponent, defineComponent,
PropType, PropType,
ref, ref,
Ref,
reactive, reactive,
createApp createApp
} from './index' } from './index'
@ -65,15 +64,12 @@ describe('with object props', () => {
// setup context // setup context
return { return {
c: ref(1), c: ref(1),
d: reactive({ d: {
e: ref('hi') e: ref('hi')
}), },
f: reactive({ f: reactive({
g: ref('hello' as GT) g: ref('hello' as GT)
}), })
h: {
i: ref('hi')
}
} }
}, },
render() { render() {
@ -106,9 +102,6 @@ describe('with object props', () => {
expectType<string>(this.d.e) expectType<string>(this.d.e)
expectType<GT>(this.f.g) expectType<GT>(this.f.g)
// should not unwrap refs nested under non-reactive objects
expectType<Ref<string>>(this.h.i)
// setup context properties should be mutable // setup context properties should be mutable
this.c = 2 this.c = 2