From 6c7cbb0dc9a542a7469732f5c9e9cfd2313ad478 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 5 Sep 2019 18:32:19 -0400 Subject: [PATCH] types: avoid plain objects with value being mistaken as refs --- packages/reactivity/src/collectionHandlers.ts | 16 +++++----- packages/reactivity/src/computed.ts | 31 +++++++++++++------ packages/reactivity/src/index.ts | 2 +- packages/reactivity/src/ref.ts | 5 +++ 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/packages/reactivity/src/collectionHandlers.ts b/packages/reactivity/src/collectionHandlers.ts index 34dc28e7..d30f8972 100644 --- a/packages/reactivity/src/collectionHandlers.ts +++ b/packages/reactivity/src/collectionHandlers.ts @@ -16,7 +16,7 @@ function get(target: any, key: any, wrap: (t: any) => any): any { return wrap(res) } -function has(key: any): boolean { +function has(this: any, key: any): boolean { const target = toRaw(this) key = toRaw(key) const proto: any = Reflect.getPrototypeOf(target) @@ -31,7 +31,7 @@ function size(target: any) { return Reflect.get(proto, 'size', target) } -function add(value: any) { +function add(this: any, value: any) { value = toRaw(value) const target = toRaw(this) const proto: any = Reflect.getPrototypeOf(this) @@ -48,7 +48,7 @@ function add(value: any) { return result } -function set(key: any, value: any) { +function set(this: any, key: any, value: any) { value = toRaw(value) const target = toRaw(this) const proto: any = Reflect.getPrototypeOf(this) @@ -75,7 +75,7 @@ function set(key: any, value: any) { return result } -function deleteEntry(key: any) { +function deleteEntry(this: any, key: any) { const target = toRaw(this) const proto: any = Reflect.getPrototypeOf(this) const hadKey = proto.has.call(target, key) @@ -93,7 +93,7 @@ function deleteEntry(key: any) { return result } -function clear() { +function clear(this: any) { const target = toRaw(this) const proto: any = Reflect.getPrototypeOf(this) const hadItems = target.size !== 0 @@ -112,7 +112,7 @@ function clear() { } function createForEach(isReadonly: boolean) { - return function forEach(callback: Function, thisArg?: any) { + return function forEach(this: any, callback: Function, thisArg?: any) { const observed = this const target = toRaw(observed) const proto: any = Reflect.getPrototypeOf(target) @@ -129,7 +129,7 @@ function createForEach(isReadonly: boolean) { } function createIterableMethod(method: string | symbol, isReadonly: boolean) { - return function(...args: any[]) { + return function(this: any, ...args: any[]) { const target = toRaw(this) const proto: any = Reflect.getPrototypeOf(target) const isPair = @@ -163,7 +163,7 @@ function createReadonlyMethod( method: Function, type: OperationTypes ): Function { - return function(...args: any[]) { + return function(this: any, ...args: any[]) { if (LOCKED) { if (__DEV__) { const key = args[0] ? `on key "${args[0]}" ` : `` diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 5504ac79..15c47a43 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,27 +1,38 @@ import { effect, ReactiveEffect, activeReactiveEffectStack } from './effect' -import { UnwrapNestedRefs, knownRefs, Ref } from './ref' +import { UnwrapNestedRefs, isRefSymbol, knownRefs } from './ref' import { isFunction } from '@vue/shared' export interface ComputedRef { + [isRefSymbol]: true readonly value: UnwrapNestedRefs readonly effect: ReactiveEffect } -export interface ComputedOptions { +export interface WritableComputedRef { + [isRefSymbol]: true + value: UnwrapNestedRefs + readonly effect: ReactiveEffect +} + +export interface WritableComputedOptions { get: () => T set: (v: T) => void } export function computed(getter: () => T): ComputedRef -export function computed(options: ComputedOptions): Ref export function computed( - getterOrOptions: (() => T) | ComputedOptions -): Ref { + options: WritableComputedOptions +): WritableComputedRef +export function computed( + getterOrOptions: (() => T) | WritableComputedOptions +): any { const isReadonly = isFunction(getterOrOptions) const getter = isReadonly ? (getterOrOptions as (() => T)) - : (getterOrOptions as ComputedOptions).get - const setter = isReadonly ? null : (getterOrOptions as ComputedOptions).set + : (getterOrOptions as WritableComputedOptions).get + const setter = isReadonly + ? null + : (getterOrOptions as WritableComputedOptions).set let dirty: boolean = true let value: any = undefined @@ -34,7 +45,7 @@ export function computed( dirty = true } }) - const computedValue = { + const computedRef = { // expose effect so computed can be stopped effect: runner, get value() { @@ -56,8 +67,8 @@ export function computed( } } } - knownRefs.add(computedValue) - return computedValue + knownRefs.add(computedRef) + return computedRef } function trackChildRun(childRunner: ReactiveEffect) { diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index f98f36a2..60103d4c 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -8,7 +8,7 @@ export { markReadonly, markNonReactive } from './reactive' -export { computed, ComputedRef, ComputedOptions } from './computed' +export { computed, ComputedRef, WritableComputedOptions } from './computed' export { effect, stop, diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 021fbe09..ed4ed386 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -5,7 +5,12 @@ import { reactive } from './reactive' export const knownRefs = new WeakSet() +export const isRefSymbol = Symbol() + export interface Ref { + // this is a type-only field to avoid objects with 'value' property being + // treated as a ref by TypeScript + [isRefSymbol]: true value: UnwrapNestedRefs }