From da4257ca2d9094dbabad74a92b6cfe53e1f0445e Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Fri, 1 Oct 2021 11:36:14 +0200 Subject: [PATCH 1/4] feat(computed): allow differentiating refs from computed Add an `effect` property on computed to differentiate them from refs --- src/apis/computed.ts | 10 ++-------- src/reactivity/index.ts | 2 ++ src/reactivity/ref.ts | 32 +++++++++++++++++++++++++++++++- test/apis/computed.spec.js | 28 +++++++++++++++++++++++++++- 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/apis/computed.ts b/src/apis/computed.ts index 2cac40a8..0f26d109 100644 --- a/src/apis/computed.ts +++ b/src/apis/computed.ts @@ -1,5 +1,5 @@ import { getVueConstructor } from '../runtimeContext' -import { createRef, Ref } from '../reactivity' +import { createRef, ComputedRef, WritableComputedRef } from '../reactivity' import { warn, noopFn, @@ -9,12 +9,6 @@ import { } from '../utils' import { getCurrentScopeVM } from './effectScope' -export interface ComputedRef extends WritableComputedRef { - readonly value: T -} - -export interface WritableComputedRef extends Ref {} - export type ComputedGetter = (ctx?: any) => T export type ComputedSetter = (v: T) => void @@ -102,6 +96,6 @@ export function computed( get: computedGetter, set: computedSetter, }, - !setter + true ) } diff --git a/src/reactivity/index.ts b/src/reactivity/index.ts index 71f53de8..c9b2ea19 100644 --- a/src/reactivity/index.ts +++ b/src/reactivity/index.ts @@ -24,6 +24,8 @@ export { del } from './del' export type { Ref, + ComputedRef, + WritableComputedRef, ToRefs, UnwrapRef, UnwrapRefSimple, diff --git a/src/reactivity/ref.ts b/src/reactivity/ref.ts index b251f693..a662e8ca 100644 --- a/src/reactivity/ref.ts +++ b/src/reactivity/ref.ts @@ -9,6 +9,19 @@ export interface Ref { value: T } +export interface WritableComputedRef extends Ref { + /** + * `effect` is added to be able to differentiate refs from computed properties. + * **Differently from Vue 3, it's just `true`**. This is because there is no equivalent + * of `ReactiveEffect` in `@vue/composition-api`. + */ + effect: true +} + +export interface ComputedRef extends WritableComputedRef { + readonly value: T +} + export type ToRefs = { [K in keyof T]: Ref } export type CollectionTypes = IterableCollections | WeakCollections @@ -58,8 +71,25 @@ export class RefImpl implements Ref { } } -export function createRef(options: RefOption, readonly = false) { +export function createRef(options: RefOption): RefImpl +export function createRef( + options: RefOption, + isComputed: true +): ComputedRef | WritableComputedRef +export function createRef( + options: RefOption, + isComputed: false +): RefImpl +export function createRef( + options: RefOption, + isComputed = false +): RefImpl | ComputedRef | WritableComputedRef { const r = new RefImpl(options) + + // add effect to differentiate refs from computed + if (isComputed) { + ;(r as WritableComputedRef | ComputedRef).effect = true + } // seal the ref, this could prevent ref from being observed // It's safe to seal the ref, since we really shouldn't extend it. // related issues: #79 diff --git a/test/apis/computed.spec.js b/test/apis/computed.spec.js index 78606e4f..af0ce83b 100644 --- a/test/apis/computed.spec.js +++ b/test/apis/computed.spec.js @@ -1,5 +1,5 @@ const Vue = require('vue/dist/vue.common.js') -const { ref, computed, isReadonly } = require('../../src') +const { ref, computed, isReadonly, reactive, isRef } = require('../../src') describe('Hooks computed', () => { beforeEach(() => { @@ -212,4 +212,30 @@ describe('Hooks computed', () => { expect(isReadonly(z.value)).toBe(false) expect(isReadonly(z.value.a)).toBe(false) }) + + it('passes isComputed', () => { + function isComputed(o) { + return !!(o && isRef(o) && o.effect) + } + + expect(isComputed(computed(() => 2))).toBe(true) + expect( + isComputed( + computed({ + get: () => 2, + set: () => {}, + }) + ) + ).toBe(true) + + expect(isComputed(ref({}))).toBe(false) + expect(isComputed(reactive({}))).toBe(false) + expect(isComputed({})).toBe(false) + expect(isComputed(undefined)).toBe(false) + expect(isComputed(null)).toBe(false) + expect(isComputed(true)).toBe(false) + expect(isComputed(20)).toBe(false) + expect(isComputed('hey')).toBe(false) + expect(isComputed('')).toBe(false) + }) }) From c59679943d0ffa47e906ef422b89ef9407466c16 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Fri, 1 Oct 2021 11:45:52 +0200 Subject: [PATCH 2/4] docs: computed().effect --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 4b30be8e..96a11e1f 100644 --- a/README.md +++ b/README.md @@ -445,6 +445,24 @@ defineComponent({ +### `computed().effect` + +
+ +⚠️ computed() has a property effect set to true instead of a ReactiveEffect. + + +Due to the difference in implementation, there is no such concept as a `ReactiveEffect` in `@vue/composition-api`. Therefore, `effect` is merely `true` to enable differentiating computed from refs: + +```ts +function isComputed(o: ComputedRef | unknown): o is ComputedRef +function isComputed(o: any): o is ComputedRef { + return !!(isRef(o) && o.effect) +} +``` + +
+ ### Missing APIs The following APIs introduced in Vue 3 are not available in this plugin. From d73b5272b43a19f190d13359c101e07dd70fdb68 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Fri, 1 Oct 2021 11:51:36 +0200 Subject: [PATCH 3/4] refactor: correct import --- src/apis/watch.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/apis/watch.ts b/src/apis/watch.ts index 9c165f58..c8971c37 100644 --- a/src/apis/watch.ts +++ b/src/apis/watch.ts @@ -1,5 +1,5 @@ import { ComponentInstance } from '../component' -import { Ref, isRef, isReactive } from '../reactivity' +import { Ref, isRef, isReactive, ComputedRef } from '../reactivity' import { assert, logError, @@ -18,7 +18,6 @@ import { WatcherPreFlushQueueKey, WatcherPostFlushQueueKey, } from '../utils/symbols' -import { ComputedRef } from './computed' import { getCurrentScopeVM } from './effectScope' export type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void From 27907841a00ad78c6bf874652ecff9b5281c1738 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Tue, 5 Oct 2021 08:32:48 +0800 Subject: [PATCH 4/4] chore: fix test --- src/apis/computed.ts | 3 ++- src/reactivity/ref.ts | 19 +++++-------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/apis/computed.ts b/src/apis/computed.ts index 0f26d109..fc85a5ee 100644 --- a/src/apis/computed.ts +++ b/src/apis/computed.ts @@ -96,6 +96,7 @@ export function computed( get: computedGetter, set: computedSetter, }, + !setter, true - ) + ) as WritableComputedRef | ComputedRef } diff --git a/src/reactivity/ref.ts b/src/reactivity/ref.ts index eceff424..210d7f80 100644 --- a/src/reactivity/ref.ts +++ b/src/reactivity/ref.ts @@ -71,31 +71,22 @@ export class RefImpl implements Ref { } } -export function createRef(options: RefOption): RefImpl -export function createRef( - options: RefOption, - isComputed: true -): ComputedRef | WritableComputedRef -export function createRef( - options: RefOption, - isComputed: false -): RefImpl export function createRef( options: RefOption, + isReadonly = false, isComputed = false -): RefImpl | ComputedRef | WritableComputedRef { +): RefImpl { const r = new RefImpl(options) // add effect to differentiate refs from computed - if (isComputed) { - ;(r as WritableComputedRef | ComputedRef).effect = true - } + if (isComputed) (r as ComputedRef).effect = true + // seal the ref, this could prevent ref from being observed // It's safe to seal the ref, since we really shouldn't extend it. // related issues: #79 const sealed = Object.seal(r) - if (readonly) readonlySet.set(sealed, true) + if (isReadonly) readonlySet.set(sealed, true) return sealed }