From 68b5d97cc888df58156e9910fa5d71f137b21e91 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Tue, 5 Oct 2021 02:46:00 +0200 Subject: [PATCH] feat(computed): allow differentiating refs from computed (#820) Co-authored-by: Anthony Fu --- README.md | 18 ++++++++++++++++++ src/apis/computed.ts | 13 ++++--------- src/apis/watch.ts | 3 +-- src/reactivity/index.ts | 2 ++ src/reactivity/ref.ts | 25 +++++++++++++++++++++++-- test/apis/computed.spec.js | 28 +++++++++++++++++++++++++++- 6 files changed, 75 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 6528db2c..1f617ec5 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. diff --git a/src/apis/computed.ts b/src/apis/computed.ts index 2cac40a8..fc85a5ee 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,7 @@ export function computed( get: computedGetter, set: computedSetter, }, - !setter - ) + !setter, + true + ) as WritableComputedRef | ComputedRef } diff --git a/src/apis/watch.ts b/src/apis/watch.ts index 91f68c3f..633cdd1e 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 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 bf49d1c7..210d7f80 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,14 +71,22 @@ export class RefImpl implements Ref { } } -export function createRef(options: RefOption, readonly = false) { +export function createRef( + options: RefOption, + isReadonly = false, + isComputed = false +): RefImpl { const r = new RefImpl(options) + + // add effect to differentiate refs from computed + 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 } 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) + }) })