Skip to content

Commit 68b5d97

Browse files
posvaantfu
andauthored
feat(computed): allow differentiating refs from computed (#820)
Co-authored-by: Anthony Fu <[email protected]>
1 parent 9c9f8e8 commit 68b5d97

File tree

6 files changed

+75
-14
lines changed

6 files changed

+75
-14
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,24 @@ defineComponent({
445445

446446
</details>
447447

448+
### `computed().effect`
449+
450+
<details>
451+
<summary>
452+
⚠️ <code>computed()</code> has a property <code>effect</code> set to <code>true</code> instead of a <code>ReactiveEffect<T></code>.
453+
</summary>
454+
455+
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:
456+
457+
```ts
458+
function isComputed<T>(o: ComputedRef<T> | unknown): o is ComputedRef<T>
459+
function isComputed(o: any): o is ComputedRef {
460+
return !!(isRef(o) && o.effect)
461+
}
462+
```
463+
464+
</details>
465+
448466
### Missing APIs
449467

450468
The following APIs introduced in Vue 3 are not available in this plugin.

src/apis/computed.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getVueConstructor } from '../runtimeContext'
2-
import { createRef, Ref } from '../reactivity'
2+
import { createRef, ComputedRef, WritableComputedRef } from '../reactivity'
33
import {
44
warn,
55
noopFn,
@@ -9,12 +9,6 @@ import {
99
} from '../utils'
1010
import { getCurrentScopeVM } from './effectScope'
1111

12-
export interface ComputedRef<T = any> extends WritableComputedRef<T> {
13-
readonly value: T
14-
}
15-
16-
export interface WritableComputedRef<T> extends Ref<T> {}
17-
1812
export type ComputedGetter<T> = (ctx?: any) => T
1913
export type ComputedSetter<T> = (v: T) => void
2014

@@ -102,6 +96,7 @@ export function computed<T>(
10296
get: computedGetter,
10397
set: computedSetter,
10498
},
105-
!setter
106-
)
99+
!setter,
100+
true
101+
) as WritableComputedRef<T> | ComputedRef<T>
107102
}

src/apis/watch.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ComponentInstance } from '../component'
2-
import { Ref, isRef, isReactive } from '../reactivity'
2+
import { Ref, isRef, isReactive, ComputedRef } from '../reactivity'
33
import {
44
assert,
55
logError,
@@ -18,7 +18,6 @@ import {
1818
WatcherPreFlushQueueKey,
1919
WatcherPostFlushQueueKey,
2020
} from '../utils/symbols'
21-
import { ComputedRef } from './computed'
2221
import { getCurrentScopeVM } from './effectScope'
2322

2423
export type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void

src/reactivity/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export { del } from './del'
2424

2525
export type {
2626
Ref,
27+
ComputedRef,
28+
WritableComputedRef,
2729
ToRefs,
2830
UnwrapRef,
2931
UnwrapRefSimple,

src/reactivity/ref.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ export interface Ref<T = any> {
99
value: T
1010
}
1111

12+
export interface WritableComputedRef<T> extends Ref<T> {
13+
/**
14+
* `effect` is added to be able to differentiate refs from computed properties.
15+
* **Differently from Vue 3, it's just `true`**. This is because there is no equivalent
16+
* of `ReactiveEffect<T>` in `@vue/composition-api`.
17+
*/
18+
effect: true
19+
}
20+
21+
export interface ComputedRef<T = any> extends WritableComputedRef<T> {
22+
readonly value: T
23+
}
24+
1225
export type ToRefs<T = any> = { [K in keyof T]: Ref<T[K]> }
1326

1427
export type CollectionTypes = IterableCollections | WeakCollections
@@ -58,14 +71,22 @@ export class RefImpl<T> implements Ref<T> {
5871
}
5972
}
6073

61-
export function createRef<T>(options: RefOption<T>, readonly = false) {
74+
export function createRef<T>(
75+
options: RefOption<T>,
76+
isReadonly = false,
77+
isComputed = false
78+
): RefImpl<T> {
6279
const r = new RefImpl<T>(options)
80+
81+
// add effect to differentiate refs from computed
82+
if (isComputed) (r as ComputedRef<T>).effect = true
83+
6384
// seal the ref, this could prevent ref from being observed
6485
// It's safe to seal the ref, since we really shouldn't extend it.
6586
// related issues: #79
6687
const sealed = Object.seal(r)
6788

68-
if (readonly) readonlySet.set(sealed, true)
89+
if (isReadonly) readonlySet.set(sealed, true)
6990

7091
return sealed
7192
}

test/apis/computed.spec.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const Vue = require('vue/dist/vue.common.js')
2-
const { ref, computed, isReadonly } = require('../../src')
2+
const { ref, computed, isReadonly, reactive, isRef } = require('../../src')
33

44
describe('Hooks computed', () => {
55
beforeEach(() => {
@@ -212,4 +212,30 @@ describe('Hooks computed', () => {
212212
expect(isReadonly(z.value)).toBe(false)
213213
expect(isReadonly(z.value.a)).toBe(false)
214214
})
215+
216+
it('passes isComputed', () => {
217+
function isComputed(o) {
218+
return !!(o && isRef(o) && o.effect)
219+
}
220+
221+
expect(isComputed(computed(() => 2))).toBe(true)
222+
expect(
223+
isComputed(
224+
computed({
225+
get: () => 2,
226+
set: () => {},
227+
})
228+
)
229+
).toBe(true)
230+
231+
expect(isComputed(ref({}))).toBe(false)
232+
expect(isComputed(reactive({}))).toBe(false)
233+
expect(isComputed({})).toBe(false)
234+
expect(isComputed(undefined)).toBe(false)
235+
expect(isComputed(null)).toBe(false)
236+
expect(isComputed(true)).toBe(false)
237+
expect(isComputed(20)).toBe(false)
238+
expect(isComputed('hey')).toBe(false)
239+
expect(isComputed('')).toBe(false)
240+
})
215241
})

0 commit comments

Comments
 (0)