Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(computed): allow differentiating refs from computed (#820)
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
posva and antfu committed Oct 5, 2021
1 parent 9c9f8e8 commit 68b5d97
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 14 deletions.
18 changes: 18 additions & 0 deletions README.md
Expand Up @@ -445,6 +445,24 @@ defineComponent({

</details>

### `computed().effect`

<details>
<summary>
⚠️ <code>computed()</code> has a property <code>effect</code> set to <code>true</code> instead of a <code>ReactiveEffect<T></code>.
</summary>

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<T>(o: ComputedRef<T> | unknown): o is ComputedRef<T>
function isComputed(o: any): o is ComputedRef {
return !!(isRef(o) && o.effect)
}
```

</details>

### Missing APIs

The following APIs introduced in Vue 3 are not available in this plugin.
Expand Down
13 changes: 4 additions & 9 deletions 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,
Expand All @@ -9,12 +9,6 @@ import {
} from '../utils'
import { getCurrentScopeVM } from './effectScope'

export interface ComputedRef<T = any> extends WritableComputedRef<T> {
readonly value: T
}

export interface WritableComputedRef<T> extends Ref<T> {}

export type ComputedGetter<T> = (ctx?: any) => T
export type ComputedSetter<T> = (v: T) => void

Expand Down Expand Up @@ -102,6 +96,7 @@ export function computed<T>(
get: computedGetter,
set: computedSetter,
},
!setter
)
!setter,
true
) as WritableComputedRef<T> | ComputedRef<T>
}
3 changes: 1 addition & 2 deletions 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,
Expand All @@ -18,7 +18,6 @@ import {
WatcherPreFlushQueueKey,
WatcherPostFlushQueueKey,
} from '../utils/symbols'
import { ComputedRef } from './computed'
import { getCurrentScopeVM } from './effectScope'

export type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void
Expand Down
2 changes: 2 additions & 0 deletions src/reactivity/index.ts
Expand Up @@ -24,6 +24,8 @@ export { del } from './del'

export type {
Ref,
ComputedRef,
WritableComputedRef,
ToRefs,
UnwrapRef,
UnwrapRefSimple,
Expand Down
25 changes: 23 additions & 2 deletions src/reactivity/ref.ts
Expand Up @@ -9,6 +9,19 @@ export interface Ref<T = any> {
value: T
}

export interface WritableComputedRef<T> extends Ref<T> {
/**
* `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<T>` in `@vue/composition-api`.
*/
effect: true
}

export interface ComputedRef<T = any> extends WritableComputedRef<T> {
readonly value: T
}

export type ToRefs<T = any> = { [K in keyof T]: Ref<T[K]> }

export type CollectionTypes = IterableCollections | WeakCollections
Expand Down Expand Up @@ -58,14 +71,22 @@ export class RefImpl<T> implements Ref<T> {
}
}

export function createRef<T>(options: RefOption<T>, readonly = false) {
export function createRef<T>(
options: RefOption<T>,
isReadonly = false,
isComputed = false
): RefImpl<T> {
const r = new RefImpl<T>(options)

// add effect to differentiate refs from computed
if (isComputed) (r as ComputedRef<T>).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
}
Expand Down
28 changes: 27 additions & 1 deletion 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(() => {
Expand Down Expand Up @@ -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)
})
})

0 comments on commit 68b5d97

Please sign in to comment.