feat(reactivity): expose unref and shallowRef

This commit is contained in:
Evan You 2020-02-22 04:39:32 +01:00
parent 0c67201942
commit e9024bf1b7
6 changed files with 59 additions and 11 deletions

View File

@ -1,5 +1,14 @@
import { ref, effect, reactive, isRef, toRefs, Ref } from '../src/index' import {
ref,
effect,
reactive,
isRef,
toRefs,
Ref,
isReactive
} from '../src/index'
import { computed } from '@vue/runtime-dom' import { computed } from '@vue/runtime-dom'
import { shallowRef, unref } from '../src/ref'
describe('reactivity/ref', () => { describe('reactivity/ref', () => {
it('should hold a value', () => { it('should hold a value', () => {
@ -129,6 +138,26 @@ describe('reactivity/ref', () => {
expect(tupleRef.value[4].value).toBe(1) expect(tupleRef.value[4].value).toBe(1)
}) })
test('unref', () => {
expect(unref(1)).toBe(1)
expect(unref(ref(1))).toBe(1)
})
test('shallowRef', () => {
const sref = shallowRef({ a: 1 })
expect(isReactive(sref.value)).toBe(false)
let dummy
effect(() => {
dummy = sref.value.a
})
expect(dummy).toBe(1)
sref.value = { a: 2 }
expect(isReactive(sref.value)).toBe(false)
expect(dummy).toBe(2)
})
test('isRef', () => { test('isRef', () => {
expect(isRef(ref(1))).toBe(true) expect(isRef(ref(1))).toBe(true)
expect(isRef(computed(() => 1))).toBe(true) expect(isRef(computed(() => 1))).toBe(true)

View File

@ -1,4 +1,4 @@
export { ref, isRef, toRefs, Ref, UnwrapRef } from './ref' export { ref, unref, shallowRef, isRef, toRefs, Ref, UnwrapRef } from './ref'
export { export {
reactive, reactive,
isReactive, isReactive,

View File

@ -31,10 +31,22 @@ export function isRef(r: any): r is Ref {
export function ref<T>(value: T): T extends Ref ? T : Ref<T> export function ref<T>(value: T): T extends Ref ? T : Ref<T>
export function ref<T = any>(): Ref<T> export function ref<T = any>(): Ref<T>
export function ref(value?: unknown) { export function ref(value?: unknown) {
return createRef(value)
}
export function shallowRef<T>(value: T): T extends Ref ? T : Ref<T>
export function shallowRef<T = any>(): Ref<T>
export function shallowRef(value?: unknown) {
return createRef(value, true)
}
function createRef(value: unknown, shallow = false) {
if (isRef(value)) { if (isRef(value)) {
return value return value
} }
value = convert(value) if (!shallow) {
value = convert(value)
}
const r = { const r = {
_isRef: true, _isRef: true,
get value() { get value() {
@ -42,7 +54,7 @@ export function ref(value?: unknown) {
return value return value
}, },
set value(newVal) { set value(newVal) {
value = convert(newVal) value = shallow ? newVal : convert(newVal)
trigger( trigger(
r, r,
TriggerOpTypes.SET, TriggerOpTypes.SET,
@ -54,6 +66,10 @@ export function ref(value?: unknown) {
return r return r
} }
export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
return isRef(ref) ? (ref.value as any) : ref
}
export function toRefs<T extends object>( export function toRefs<T extends object>(
object: T object: T
): { [K in keyof T]: Ref<T[K]> } { ): { [K in keyof T]: Ref<T[K]> } {

View File

@ -13,7 +13,8 @@ import {
isRef, isRef,
isReactive, isReactive,
Ref, Ref,
ComputedRef ComputedRef,
unref
} from '@vue/reactivity' } from '@vue/reactivity'
import { warn } from './warning' import { warn } from './warning'
import { Slots } from './componentSlots' import { Slots } from './componentSlots'
@ -84,8 +85,6 @@ const enum AccessTypes {
OTHER OTHER
} }
const unwrapRef = (val: unknown) => (isRef(val) ? val.value : val)
export const PublicInstanceProxyHandlers: ProxyHandler<any> = { export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get(target: ComponentInternalInstance, key: string) { get(target: ComponentInternalInstance, key: string) {
// fast path for unscopables when using `with` block // fast path for unscopables when using `with` block
@ -115,7 +114,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 unwrapRef(renderContext[key]) return unref(renderContext[key])
case AccessTypes.PROPS: case AccessTypes.PROPS:
return propsProxy![key] return propsProxy![key]
// default: just fallthrough // default: just fallthrough
@ -125,7 +124,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
return data[key] return data[key]
} else if (hasOwn(renderContext, key)) { } else if (hasOwn(renderContext, key)) {
accessCache![key] = AccessTypes.CONTEXT accessCache![key] = AccessTypes.CONTEXT
return unwrapRef(renderContext[key]) return unref(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

View File

@ -3,6 +3,8 @@
export const version = __VERSION__ export const version = __VERSION__
export { export {
ref, ref,
unref,
shallowRef,
isRef, isRef,
toRefs, toRefs,
reactive, reactive,

View File

@ -1,6 +1,5 @@
import { expectType } from 'tsd' import { expectType } from 'tsd'
import { Ref, ref } from './index' import { Ref, ref, isRef, unref } from './index'
import { isRef } from '@vue/reactivity'
function foo(arg: number | Ref<number>) { function foo(arg: number | Ref<number>) {
// ref coercing // ref coercing
@ -11,6 +10,9 @@ function foo(arg: number | Ref<number>) {
if (isRef(arg)) { if (isRef(arg)) {
expectType<Ref<number>>(arg) expectType<Ref<number>>(arg)
} }
// ref unwrapping
expectType<number>(unref(arg))
} }
foo(1) foo(1)