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

fix(toRefs): don't trigger unwanted watchEffects #3260

Merged
merged 2 commits into from Jul 30, 2023
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
40 changes: 39 additions & 1 deletion packages/shared/toRefs/index.test.ts
@@ -1,4 +1,4 @@
import { computed, isVue3, reactive, ref } from 'vue-demi'
import { computed, isVue3, reactive, ref, watchSyncEffect } from 'vue-demi'
import { describe, expect, it, vi } from 'vitest'
import { toRefs } from '.'

Expand Down Expand Up @@ -98,6 +98,44 @@ describe('toRefs', () => {
expect(spy).toHaveBeenLastCalledWith(['a', 1])
})

it('should trigger unwanted effects with replaceRef = false', () => {
const spy = vi.fn()
const obj = ref({
a: 'a',
b: 0,
})

const { a, b } = toRefs(obj, { replaceRef: true })
expect(a.value).toBe('a')
expect(b.value).toBe(0)

watchSyncEffect(() => spy(a.value))

expect(spy).toHaveBeenCalledTimes(1)

b.value = 1
expect(spy).toHaveBeenCalledTimes(2)
})

it('should not trigger unwanted effects with replaceRef = false', () => {
const spy = vi.fn()
const obj = ref({
a: 'a',
b: 0,
})

const { a, b } = toRefs(obj, { replaceRef: false })
expect(a.value).toBe('a')
expect(b.value).toBe(0)

watchSyncEffect(() => spy(a.value))

expect(spy).toHaveBeenCalledTimes(1)

b.value = 1
expect(spy).toHaveBeenCalledTimes(1)
})

it('should save instance of class', () => {
class SomeClass {
v = 1
Expand Down
38 changes: 28 additions & 10 deletions packages/shared/toRefs/index.ts
@@ -1,6 +1,16 @@
import type { ToRefs } from 'vue-demi'
import { toRefs as _toRefs, customRef, isRef } from 'vue-demi'
import type { MaybeRef } from '../utils'
import { toValue } from '../toValue'
import type { MaybeRef, MaybeRefOrGetter } from '../utils'

export interface ToRefsOptions {
/**
* Replace the original ref with a copy on property update.
*
* @default true
*/
replaceRef?: MaybeRefOrGetter<boolean>
}

/**
* Extended `toRefs` that also accepts refs of an object.
Expand All @@ -10,6 +20,7 @@ import type { MaybeRef } from '../utils'
*/
export function toRefs<T extends object>(
objectRef: MaybeRef<T>,
options: ToRefsOptions = {},
): ToRefs<T> {
if (!isRef(objectRef))
return _toRefs(objectRef)
Expand All @@ -24,17 +35,24 @@ export function toRefs<T extends object>(
return objectRef.value[key]
},
set(v) {
if (Array.isArray(objectRef.value)) {
const copy: any = [...objectRef.value]
copy[key] = v
objectRef.value = copy
}
else {
const newObject = { ...objectRef.value, [key]: v }
const replaceRef = toValue(options.replaceRef) ?? true

Object.setPrototypeOf(newObject, Object.getPrototypeOf(objectRef.value))
if (replaceRef) {
if (Array.isArray(objectRef.value)) {
const copy: any = [...objectRef.value]
copy[key] = v
objectRef.value = copy
}
else {
const newObject = { ...objectRef.value, [key]: v }

objectRef.value = newObject
Object.setPrototypeOf(newObject, Object.getPrototypeOf(objectRef.value))

objectRef.value = newObject
}
}
else {
objectRef.value[key] = v
}
},
}))
Expand Down