Skip to content

Commit

Permalink
feat: proxyRefs method and ShallowUnwrapRefs type (#456)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
pikax committed Aug 6, 2020
1 parent 95d87f1 commit 149821a
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 141 deletions.
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ watch(() => {

</details>

### createApp
### `createApp`

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

</details>

### shallowReadonly
### `shallowReadonly`

<details>
<summary>
Expand All @@ -392,6 +392,29 @@ app2.component('Bar', Bar) // equivalent to Vue.use('Bar', Bar)
</details>

### `props`
<details>
<summary>
⚠️ <code>toRefs(props.foo.bar)</code> will incorrectly warn when acessing nested levels of props.
⚠️ <code>isReactive(props.foo.bar)</code> will return false.
</summary>

```ts
defineComponent({
setup(props) {
const { bar } = toRefs(props.foo) // it will `warn`

// use this instead
const { foo } = toRefs(props)
const a = foo.value.bar
}
})
```

</details>



### Missing APIs

The following APIs introduced in Vue 3 are not available in this plugin.
Expand Down
2 changes: 2 additions & 0 deletions src/apis/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ export {
UnwrapRef,
isReadonly,
shallowReadonly,
proxyRefs,
ShallowUnwrapRef,
} from '../reactivity'
8 changes: 4 additions & 4 deletions src/component/componentProxy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ExtractPropTypes } from './componentProps'
import { UnwrapRef } from '..'
import { ShallowUnwrapRef } from '..'
import { Data } from './common'

import Vue, {
Expand Down Expand Up @@ -28,7 +28,7 @@ export type ComponentRenderProxy<
$props: Readonly<P & PublicProps>
$attrs: Data
} & Readonly<P> &
UnwrapRef<B> &
ShallowUnwrapRef<B> &
D &
M &
ExtractComputedReturns<C> &
Expand All @@ -38,7 +38,7 @@ export type ComponentRenderProxy<
type VueConstructorProxy<PropsOptions, RawBindings> = VueConstructor & {
new (...args: any[]): ComponentRenderProxy<
ExtractPropTypes<PropsOptions>,
UnwrapRef<RawBindings>,
ShallowUnwrapRef<RawBindings>,
ExtractPropTypes<PropsOptions, false>
>
}
Expand All @@ -55,7 +55,7 @@ export type VueProxy<
Methods = DefaultMethods<Vue>
> = Vue2ComponentOptions<
Vue,
UnwrapRef<RawBindings> & Data,
ShallowUnwrapRef<RawBindings> & Data,
Methods,
Computed,
PropsOptions,
Expand Down
16 changes: 3 additions & 13 deletions src/mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@ import {
SetupFunction,
Data,
} from './component'
import {
isRef,
isReactive,
markRaw,
unwrapRefProxy,
markReactive,
} from './reactivity'
import { isRef, isReactive, markRaw, markReactive } from './reactivity'
import { isPlainObject, assert, proxy, warn, isFunction } from './utils'
import { ref } from './apis'
import vmStateManager from './utils/vmStateManager'
Expand Down Expand Up @@ -79,7 +73,7 @@ export function mixin(Vue: VueConstructor) {
const setup = vm.$options.setup!
const ctx = createSetupContext(vm)

// mark props as reactive
// mark props
markReactive(props)

// resolve scopedSlots and slots to functions
Expand Down Expand Up @@ -116,12 +110,8 @@ export function mixin(Vue: VueConstructor) {
if (isFunction(bindingValue)) {
bindingValue = bindingValue.bind(vm)
}
// unwrap all ref properties
const unwrapped = unwrapRefProxy(bindingValue)
// mark the object as reactive
markReactive(unwrapped)
// a non-reactive should not don't get reactivity
bindingValue = ref(markRaw(unwrapped))
bindingValue = ref(markRaw(bindingValue))
}
}
asVmProperty(vm, name, bindingValue)
Expand Down
3 changes: 2 additions & 1 deletion src/reactivity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export {
unref,
shallowRef,
triggerRef,
proxyRefs,
ShallowUnwrapRef,
} from './ref'
export { set } from './set'
export { unwrapRefProxy } from './unwrap'
40 changes: 35 additions & 5 deletions src/reactivity/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Data } from '../component'
import { RefKey, ReadonlyIdentifierKey } from '../utils/symbols'
import { proxy, isPlainObject, warn } from '../utils'
import { reactive, isReactive, shallowReactive } from './reactive'
import { ComputedRef } from '../apis/computed'

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

export type UnwrapRef<T> = T extends ComputedRef<infer V>
? UnwrapRefSimple<V>
: T extends Ref<infer V>
export type ShallowUnwrapRef<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>

type UnwrapRefSimple<T> = T extends Function | CollectionTypes | BaseTypes | Ref
? T
: T extends Array<any>
? T
? { [K in keyof T]: UnwrapRefSimple<T[K]> }
: T extends object
? UnwrappedObject<T>
: T
Expand All @@ -50,6 +51,7 @@ type SymbolExtract<T> = (T extends { [Symbol.asyncIterator]: infer V }
: {}) &
(T extends { [Symbol.iterator]: infer V } ? { [Symbol.iterator]: V } : {}) &
(T extends { [Symbol.match]: infer V } ? { [Symbol.match]: V } : {}) &
(T extends { [Symbol.matchAll]: infer V } ? { [Symbol.matchAll]: V } : {}) &
(T extends { [Symbol.replace]: infer V } ? { [Symbol.replace]: V } : {}) &
(T extends { [Symbol.search]: infer V } ? { [Symbol.search]: V } : {}) &
(T extends { [Symbol.species]: infer V } ? { [Symbol.species]: V } : {}) &
Expand Down Expand Up @@ -188,3 +190,31 @@ export function triggerRef(value: any) {

value.value = value.value
}

export function proxyRefs<T extends object>(
objectWithRefs: T
): ShallowUnwrapRef<T> {
if (isReactive(objectWithRefs)) {
return objectWithRefs as ShallowUnwrapRef<T>
}
const value: Record<string, any> = reactive({ [RefKey]: objectWithRefs })

for (const key of Object.keys(objectWithRefs)) {
proxy(value, key, {
get() {
if (isRef(value[key])) {
return value[key].value
}
return value[key]
},
set(v: unknown) {
if (isRef(value[key])) {
return (value[key].value = unref(v))
}
value[key] = unref(v)
},
})
}

return value as ShallowUnwrapRef<T>
}
56 changes: 0 additions & 56 deletions src/reactivity/unwrap.ts

This file was deleted.

2 changes: 1 addition & 1 deletion test-dts/defineComponent.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,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
Loading

0 comments on commit 149821a

Please sign in to comment.