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(useVModel): support clone option #2022

Merged
merged 8 commits into from Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from 6 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
4 changes: 3 additions & 1 deletion packages/core/useCloned/index.ts
Expand Up @@ -29,7 +29,9 @@ export interface UseClonedReturn<T> {
sync: () => void
}

function cloneFnJSON<T>(source: T): T {
export type CloneFn<F, T = F> = (x: F) => T

export function cloneFnJSON<T>(source: T): T {
return JSON.parse(JSON.stringify(source))
}

Expand Down
52 changes: 52 additions & 0 deletions packages/core/useVModel/index.test.ts
Expand Up @@ -177,4 +177,56 @@ describe('useVModel', () => {

expect(emitValue instanceof SomeClass).toBeTruthy()
})

it('should clone object', async () => {
antfu marked this conversation as resolved.
Show resolved Hide resolved
const emitMock = vitest.fn()

const props = {
person: {
age: 18,
child: { age: 2 },
},
}

const data = useVModel(props, 'person', emitMock, { passive: true, clone: true })
const dataDeep = useVModel(props, 'person', emitMock, { passive: true, clone: true, deep: true })

data.value.age = 20

await nextTick()
expect(props.person).not.toBe(data.value)
expect(props.person).toEqual(expect.objectContaining({ age: 18 }))

dataDeep.value.child.age = 3

expect(props.person).not.toBe(dataDeep.value)
expect(props.person).toEqual(expect.objectContaining({
child: { age: 2 },
}))
})

it('should deep clone object with clone function', async () => {
const emitMock = vitest.fn()
const clone = vitest.fn(x => JSON.parse(JSON.stringify(x)))

const props = {
person: {
age: 18,
child: { age: 2 },
},
}

const data = useVModel(props, 'person', emitMock, { passive: true, clone, deep: true })

data.value.age = 20
data.value.child.age = 3

await nextTick()
expect(clone).toHaveBeenCalled()
expect(props.person).not.toBe(data.value)
expect(props.person).toEqual({
age: 18,
child: { age: 2 },
})
})
})
45 changes: 35 additions & 10 deletions packages/core/useVModel/index.ts
@@ -1,6 +1,8 @@
import { isDef } from '@vueuse/shared'
import { isDef, isFunction } from '@vueuse/shared'
import type { UnwrapRef } from 'vue-demi'
import { computed, getCurrentInstance, isVue2, ref, watch } from 'vue-demi'
import type { CloneFn } from '../useCloned'
import { cloneFnJSON } from '../useCloned'

export interface UseVModelOptions<T> {
/**
Expand Down Expand Up @@ -29,6 +31,14 @@ export interface UseVModelOptions<T> {
* @default undefined
*/
defaultValue?: T
/**
* Clone the props.
* Accepts a custom clone function.
* When setting to `true`, it will use `JSON.parse(JSON.stringify(value))` to clone.
*
* @default false
*/
clone?: boolean | CloneFn<T>
}

/**
Expand All @@ -46,6 +56,7 @@ export function useVModel<P extends object, K extends keyof P, Name extends stri
options: UseVModelOptions<P[K]> = {},
) {
const {
clone = false,
passive = false,
eventName,
deep = false,
Expand All @@ -71,19 +82,33 @@ export function useVModel<P extends object, K extends keyof P, Name extends stri

event = eventName || event || `update:${key!.toString()}`

const getValue = () => isDef(props[key!]) ? props[key!] : defaultValue
const cloneFn = (val: P[K]) => !clone
? val
: isFunction(clone)
? clone(val)
: cloneFnJSON(val)

const getValue = () => isDef(props[key!])
? cloneFn(props[key!])
: defaultValue

if (passive) {
const proxy = ref<P[K]>(getValue()!)
const initialValue = getValue()
const proxy = ref<P[K]>(initialValue!)

watch(() => props[key!], v => proxy.value = v as UnwrapRef<P[K]>)
watch(
() => props[key!],
v => proxy.value = cloneFn(v) as UnwrapRef<P[K]>,
)

watch(proxy, (v) => {
if (v !== props[key!] || deep)
_emit(event, v)
}, {
deep,
})
watch(
proxy,
(v) => {
if (v !== props[key!] || deep)
_emit(event, v)
},
{ deep },
)

return proxy
}
Expand Down