Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(computed): allow differentiating refs from computed #820

Merged
merged 5 commits into from Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
})
})