From c927fd5bda460f56ab840f3196e7dfe5098795b0 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 31 Jul 2022 14:41:17 +0300 Subject: [PATCH 01/10] feat(useObjectTemplate): new function --- packages/core/index.ts | 1 + packages/core/useObjectTemplate/demo.vue | 20 +++++++++++ packages/core/useObjectTemplate/index.md | 26 ++++++++++++++ packages/core/useObjectTemplate/index.test.ts | 36 +++++++++++++++++++ packages/core/useObjectTemplate/index.ts | 14 ++++++++ 5 files changed, 97 insertions(+) create mode 100644 packages/core/useObjectTemplate/demo.vue create mode 100644 packages/core/useObjectTemplate/index.md create mode 100644 packages/core/useObjectTemplate/index.test.ts create mode 100644 packages/core/useObjectTemplate/index.ts diff --git a/packages/core/index.ts b/packages/core/index.ts index a30573ee049..0bae253816f 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -72,6 +72,7 @@ export * from './useMutationObserver' export * from './useNavigatorLanguage' export * from './useNetwork' export * from './useNow' +export * from './useObjectTemplate' export * from './useObjectUrl' export * from './useOffsetPagination' export * from './useOnline' diff --git a/packages/core/useObjectTemplate/demo.vue b/packages/core/useObjectTemplate/demo.vue new file mode 100644 index 00000000000..8b514301178 --- /dev/null +++ b/packages/core/useObjectTemplate/demo.vue @@ -0,0 +1,20 @@ + + + diff --git a/packages/core/useObjectTemplate/index.md b/packages/core/useObjectTemplate/index.md new file mode 100644 index 00000000000..ecf53a9d965 --- /dev/null +++ b/packages/core/useObjectTemplate/index.md @@ -0,0 +1,26 @@ +--- +category: Utilities +--- + +# useObjectTemplate + +Using reactive ref to form template. + +## Usage + +```ts +import { useObjectTemplate } from '@vueuse/core/useObjectTemplate' + +const originData = ref({ + key: 'value' +}) + +const { + template, + reset +} = useObjectTemplate(originData) + +template.value = 'changedValue' + +reset() +``` diff --git a/packages/core/useObjectTemplate/index.test.ts b/packages/core/useObjectTemplate/index.test.ts new file mode 100644 index 00000000000..1a16c5109db --- /dev/null +++ b/packages/core/useObjectTemplate/index.test.ts @@ -0,0 +1,36 @@ +import { expect } from 'vitest' +import { ref } from 'vue-demi' +import { useObjectTemplate } from '.' + +const data = ref({ + key: 'keyValue', + banana: 'bananaValue', +}) + +describe('useObjectTemplate', () => { + it('work with template ref object', () => { + const { template, reset } = useObjectTemplate(data) + + expect(template.value).toEqual(data.value) + + template.value.banana = 'apple' + expect(template.value.banana).toEqual('apple') + + reset() + + expect(template.value).toEqual(data.value) + }) + + it('work with template object', () => { + const { template, reset } = useObjectTemplate(data.value) + + expect(template.value).toEqual(data.value) + + template.value.banana = 'apple' + expect(template.value.banana).toEqual('apple') + + reset() + + expect(template.value).toEqual(data.value) + }) +}) diff --git a/packages/core/useObjectTemplate/index.ts b/packages/core/useObjectTemplate/index.ts new file mode 100644 index 00000000000..bae990a5a11 --- /dev/null +++ b/packages/core/useObjectTemplate/index.ts @@ -0,0 +1,14 @@ +import type { MaybeRef } from '@vueuse/shared' +import { ref, unref } from 'vue-demi' + +export function useObjectTemplate>(originTemplate: MaybeRef) { + const template = ref({} as T) + + function reset() { + template.value = JSON.parse(JSON.stringify(unref(originTemplate))) + } + + reset() + + return { template, reset } +} From 8aa66cf5c8e81a6ce0abbd8bbdea0e5e3f0d9131 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Thu, 4 Aug 2022 23:28:52 +0300 Subject: [PATCH 02/10] feat(useCloned): new function --- packages/core/index.ts | 2 +- packages/core/useCloned/demo.vue | 16 ++++ packages/core/useCloned/index.md | 61 +++++++++++++++ packages/core/useCloned/index.test.ts | 76 +++++++++++++++++++ packages/core/useCloned/index.ts | 43 +++++++++++ packages/core/useObjectTemplate/demo.vue | 20 ----- packages/core/useObjectTemplate/index.md | 26 ------- packages/core/useObjectTemplate/index.test.ts | 36 --------- packages/core/useObjectTemplate/index.ts | 14 ---- 9 files changed, 197 insertions(+), 97 deletions(-) create mode 100644 packages/core/useCloned/demo.vue create mode 100644 packages/core/useCloned/index.md create mode 100644 packages/core/useCloned/index.test.ts create mode 100644 packages/core/useCloned/index.ts delete mode 100644 packages/core/useObjectTemplate/demo.vue delete mode 100644 packages/core/useObjectTemplate/index.md delete mode 100644 packages/core/useObjectTemplate/index.test.ts delete mode 100644 packages/core/useObjectTemplate/index.ts diff --git a/packages/core/index.ts b/packages/core/index.ts index 36bdfc82f7f..9c2770a5b6c 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -18,6 +18,7 @@ export * from './useBroadcastChannel' export * from './useBrowserLocation' export * from './useCached' export * from './useClipboard' +export * from './useCloned' export * from './useColorMode' export * from './useConfirmDialog' export * from './useCssVar' @@ -72,7 +73,6 @@ export * from './useMutationObserver' export * from './useNavigatorLanguage' export * from './useNetwork' export * from './useNow' -export * from './useObjectTemplate' export * from './useObjectUrl' export * from './useOffsetPagination' export * from './useOnline' diff --git a/packages/core/useCloned/demo.vue b/packages/core/useCloned/demo.vue new file mode 100644 index 00000000000..9246fb256fd --- /dev/null +++ b/packages/core/useCloned/demo.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/core/useCloned/index.md b/packages/core/useCloned/index.md new file mode 100644 index 00000000000..26314c4fb63 --- /dev/null +++ b/packages/core/useCloned/index.md @@ -0,0 +1,61 @@ +--- +category: Utilities +--- + +# useCloned + +Reactive partial clone. + +## Usage +```ts +import { useCloned } from '@vueuse/core/useCloned' + +const originData = ref({ + key: 'value' +}) + +const { cloned, stop } = useCloned(originData) + +originData.key = 'some new value' + +console.log(cloned.value.key) // 'some new value' + +stop() +``` + +## Manual cloning +```ts +import { useCloned } from '@vueuse/core/useCloned' + +const data = ref({ + key: 'value' +}) + +const { cloned, sync } = useCloned(data, { manual: true }) + +data.key = 'manual' + +console.log(cloned.value.key) // 'value' + +sync(); + +console.log(cloned.value.key)// 'manual' +``` + +## Custom clone function usage +```ts +import { useCloned } from '@vueuse/core/useCloned' + +const data = ref({ + key: 'value' +}) + +const { cloned, sync } = useCloned(data, { + cloneFunction: (source, cloned) => ({ ...source, isCloned: true }) +}) + +data.value.key = 'clone it'; + +console.log(cloned.value.isCloned) // true +console.log(cloned.value.key) // 'clone it' +``` diff --git a/packages/core/useCloned/index.test.ts b/packages/core/useCloned/index.test.ts new file mode 100644 index 00000000000..7bc1006d339 --- /dev/null +++ b/packages/core/useCloned/index.test.ts @@ -0,0 +1,76 @@ +import { useCloned } from '@vueuse/core' +import { expect } from 'vitest' +import { nextTick, ref } from 'vue-demi' + +describe('useCloned', () => { + it('works with simple objects', () => { + const data = { test: 'test' } + + const { cloned, sync } = useCloned(data) + + expect(cloned.value).toEqual(data) + + cloned.value = { test: 'failed' } + + sync() + + expect(cloned.value).toEqual(data) + }) + + it('works with refs', async () => { + const data = ref({ test: 'test' }) + + const { cloned } = useCloned(data) + + data.value.test = 'success' + + await nextTick() + + expect(cloned.value).toEqual(data.value) + }) + + it('works with refs and manual sync', async () => { + const data = ref({ test: 'test' }) + + const { cloned, sync } = useCloned(data, { manual: true }) + + data.value.test = 'success' + + expect(cloned.value).not.toEqual(data.value) + + sync() + + expect(cloned.value).toEqual(data.value) + }) + + it('works like partial cloning', async () => { + const data = ref({ test: 'test' }) + + const { cloned } = useCloned>(data) + + cloned.value.check = 'value' + + data.value.test = 'partial' + + await nextTick() + + expect(cloned.value.check).toBe('value') + expect(cloned.value.test).toBe('partial') + }) + + it('works with custom clone function', async () => { + const data = ref({ test: 'test' }) + + const { cloned } = useCloned>(data, { cloneFunction: (source, cloned) => ({ ...cloned, ...source, proxyTest: true }) }) + + cloned.value.check = 'value' + + data.value.test = 'partial' + + await nextTick() + + expect(cloned.value.check).toBe('value') + expect(cloned.value.test).toBe('partial') + expect(cloned.value.proxyTest).toBe(true) + }) +}) diff --git a/packages/core/useCloned/index.ts b/packages/core/useCloned/index.ts new file mode 100644 index 00000000000..d6b64d6aff7 --- /dev/null +++ b/packages/core/useCloned/index.ts @@ -0,0 +1,43 @@ +import type { MaybeRef } from '@vueuse/shared' +import type { Ref, WatchStopHandle } from 'vue-demi' +import { isRef, ref, unref, watch } from 'vue-demi' + +export interface UseClonedOptions = Record> { + /** + * sync source only by function + */ + manual?: boolean + /** + * Custom clone function should return new value for cloned data + */ + cloneFunction?: (source: Partial, cloned: Partial) => Partial | T +} + +export function useCloned = Record>(source: Ref>, options: UseClonedOptions & { manual: true }): { cloned: Ref; sync: () => void } +export function useCloned = Record>(source: Ref>, options?: UseClonedOptions & { manual: false } | Omit): { cloned: Ref; sync: () => void; stop: WatchStopHandle } +export function useCloned = Record>(source: Partial): { cloned: Ref; sync: () => void; stop: WatchStopHandle } +export function useCloned = Record>(source: MaybeRef>, options: UseClonedOptions = {}) { + const cloned = ref({} as T) + + const { manual, cloneFunction } = options + + let stopWatcher: undefined | WatchStopHandle + + if (!manual && isRef(source)) + stopWatcher = watch(source, sync, { immediate: true, deep: true }) + else + sync() + + function sync() { + if (cloneFunction) { + cloned.value = cloneFunction(unref(source), cloned.value) + + return + } + + for (const key in unref(source)) + cloned.value[key] = unref(source)[key as keyof T] + } + + return { cloned, sync, ...(stopWatcher && { stop: stopWatcher }) } as any +} diff --git a/packages/core/useObjectTemplate/demo.vue b/packages/core/useObjectTemplate/demo.vue deleted file mode 100644 index 8b514301178..00000000000 --- a/packages/core/useObjectTemplate/demo.vue +++ /dev/null @@ -1,20 +0,0 @@ - - - diff --git a/packages/core/useObjectTemplate/index.md b/packages/core/useObjectTemplate/index.md deleted file mode 100644 index ecf53a9d965..00000000000 --- a/packages/core/useObjectTemplate/index.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -category: Utilities ---- - -# useObjectTemplate - -Using reactive ref to form template. - -## Usage - -```ts -import { useObjectTemplate } from '@vueuse/core/useObjectTemplate' - -const originData = ref({ - key: 'value' -}) - -const { - template, - reset -} = useObjectTemplate(originData) - -template.value = 'changedValue' - -reset() -``` diff --git a/packages/core/useObjectTemplate/index.test.ts b/packages/core/useObjectTemplate/index.test.ts deleted file mode 100644 index 1a16c5109db..00000000000 --- a/packages/core/useObjectTemplate/index.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { expect } from 'vitest' -import { ref } from 'vue-demi' -import { useObjectTemplate } from '.' - -const data = ref({ - key: 'keyValue', - banana: 'bananaValue', -}) - -describe('useObjectTemplate', () => { - it('work with template ref object', () => { - const { template, reset } = useObjectTemplate(data) - - expect(template.value).toEqual(data.value) - - template.value.banana = 'apple' - expect(template.value.banana).toEqual('apple') - - reset() - - expect(template.value).toEqual(data.value) - }) - - it('work with template object', () => { - const { template, reset } = useObjectTemplate(data.value) - - expect(template.value).toEqual(data.value) - - template.value.banana = 'apple' - expect(template.value.banana).toEqual('apple') - - reset() - - expect(template.value).toEqual(data.value) - }) -}) diff --git a/packages/core/useObjectTemplate/index.ts b/packages/core/useObjectTemplate/index.ts deleted file mode 100644 index bae990a5a11..00000000000 --- a/packages/core/useObjectTemplate/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { MaybeRef } from '@vueuse/shared' -import { ref, unref } from 'vue-demi' - -export function useObjectTemplate>(originTemplate: MaybeRef) { - const template = ref({} as T) - - function reset() { - template.value = JSON.parse(JSON.stringify(unref(originTemplate))) - } - - reset() - - return { template, reset } -} From 7143a803a1ba6e56e15707257b13b3580adae094 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Fri, 5 Aug 2022 18:15:40 +0300 Subject: [PATCH 03/10] fix(useCloned): fix linter --- packages/core/useCloned/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/useCloned/index.md b/packages/core/useCloned/index.md index 26314c4fb63..1c0f61efdb4 100644 --- a/packages/core/useCloned/index.md +++ b/packages/core/useCloned/index.md @@ -37,7 +37,7 @@ data.key = 'manual' console.log(cloned.value.key) // 'value' -sync(); +sync() console.log(cloned.value.key)// 'manual' ``` @@ -50,11 +50,11 @@ const data = ref({ key: 'value' }) -const { cloned, sync } = useCloned(data, { - cloneFunction: (source, cloned) => ({ ...source, isCloned: true }) +const { cloned, sync } = useCloned(data, { + cloneFunction: (source, cloned) => ({ ...source, isCloned: true }) }) -data.value.key = 'clone it'; +data.value.key = 'clone it' console.log(cloned.value.isCloned) // true console.log(cloned.value.key) // 'clone it' From 0f84e03d504a65382f1c2dff86bbfb0f06adefe4 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Fri, 5 Aug 2022 19:39:31 +0300 Subject: [PATCH 04/10] feat(useCloned): remove partial cloning. Added watchOptions --- packages/core/useCloned/index.test.ts | 27 ++++++++++++++------- packages/core/useCloned/index.ts | 35 +++++++++++++++------------ 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/packages/core/useCloned/index.test.ts b/packages/core/useCloned/index.test.ts index 7bc1006d339..ec680cb87ad 100644 --- a/packages/core/useCloned/index.test.ts +++ b/packages/core/useCloned/index.test.ts @@ -43,10 +43,10 @@ describe('useCloned', () => { expect(cloned.value).toEqual(data.value) }) - it('works like partial cloning', async () => { + it('works with custom clone function', async () => { const data = ref({ test: 'test' }) - const { cloned } = useCloned>(data) + const { cloned } = useCloned>(data, { cloneFunction: (source, cloned) => ({ ...cloned, ...source, proxyTest: true }) }) cloned.value.check = 'value' @@ -56,21 +56,30 @@ describe('useCloned', () => { expect(cloned.value.check).toBe('value') expect(cloned.value.test).toBe('partial') + expect(cloned.value.proxyTest).toBe(true) }) - it('works with custom clone function', async () => { + it('works with watch options', async () => { const data = ref({ test: 'test' }) - const { cloned } = useCloned>(data, { cloneFunction: (source, cloned) => ({ ...cloned, ...source, proxyTest: true }) }) + const { cloned } = useCloned(data, { watchOptions: { immediate: false, deep: false } }) - cloned.value.check = 'value' + await nextTick() - data.value.test = 'partial' + // test immediate: false + expect(cloned.value).toEqual({}) + + data.value.test = 'not valid' await nextTick() - expect(cloned.value.check).toBe('value') - expect(cloned.value.test).toBe('partial') - expect(cloned.value.proxyTest).toBe(true) + // test deep: false + expect(cloned.value).toEqual({}) + + data.value = { test: 'valid' } + + await nextTick() + + expect(cloned.value).toEqual(data.value) }) }) diff --git a/packages/core/useCloned/index.ts b/packages/core/useCloned/index.ts index d6b64d6aff7..f5e368ee283 100644 --- a/packages/core/useCloned/index.ts +++ b/packages/core/useCloned/index.ts @@ -1,5 +1,5 @@ import type { MaybeRef } from '@vueuse/shared' -import type { Ref, WatchStopHandle } from 'vue-demi' +import type { Ref, WatchOptions, WatchStopHandle } from 'vue-demi' import { isRef, ref, unref, watch } from 'vue-demi' export interface UseClonedOptions = Record> { @@ -10,33 +10,36 @@ export interface UseClonedOptions = Record> /** * Custom clone function should return new value for cloned data */ - cloneFunction?: (source: Partial, cloned: Partial) => Partial | T + cloneFunction?: (source: T, cloned: T) => T + /** + * Options for watcher + * + * @default { immediate: true, deep: true } + */ + watchOptions?: WatchOptions } -export function useCloned = Record>(source: Ref>, options: UseClonedOptions & { manual: true }): { cloned: Ref; sync: () => void } -export function useCloned = Record>(source: Ref>, options?: UseClonedOptions & { manual: false } | Omit): { cloned: Ref; sync: () => void; stop: WatchStopHandle } -export function useCloned = Record>(source: Partial): { cloned: Ref; sync: () => void; stop: WatchStopHandle } -export function useCloned = Record>(source: MaybeRef>, options: UseClonedOptions = {}) { +export function useCloned = Record>(source: Ref, options: UseClonedOptions & { manual: true }): { cloned: Ref; sync: () => void } +export function useCloned = Record>(source: Ref, options?: UseClonedOptions & { manual: false } | Omit): { cloned: Ref; sync: () => void; stop: WatchStopHandle } +export function useCloned = Record>(source: T): { cloned: Ref; sync: () => void; stop: WatchStopHandle } +export function useCloned = Record>(source: MaybeRef, options: UseClonedOptions = {}) { const cloned = ref({} as T) - const { manual, cloneFunction } = options + const { manual, cloneFunction, watchOptions = { immediate: true, deep: true } } = options let stopWatcher: undefined | WatchStopHandle if (!manual && isRef(source)) - stopWatcher = watch(source, sync, { immediate: true, deep: true }) + stopWatcher = watch(source, sync, watchOptions) else sync() - function sync() { - if (cloneFunction) { - cloned.value = cloneFunction(unref(source), cloned.value) - - return - } + function defaultCloning() { + return JSON.parse(JSON.stringify(unref(source))) + } - for (const key in unref(source)) - cloned.value[key] = unref(source)[key as keyof T] + function sync() { + cloned.value = cloneFunction ? cloneFunction(unref(source), cloned.value) : defaultCloning() } return { cloned, sync, ...(stopWatcher && { stop: stopWatcher }) } as any From 79ebed962e8490d99f11c81d6e8d1ca090cc90c1 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 6 Aug 2022 11:52:43 +0300 Subject: [PATCH 05/10] feat(useCloned): remove stop watcher function --- packages/core/useCloned/index.md | 3 +-- packages/core/useCloned/index.ts | 11 +++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/core/useCloned/index.md b/packages/core/useCloned/index.md index 1c0f61efdb4..5369d5057fc 100644 --- a/packages/core/useCloned/index.md +++ b/packages/core/useCloned/index.md @@ -14,13 +14,12 @@ const originData = ref({ key: 'value' }) -const { cloned, stop } = useCloned(originData) +const { cloned } = useCloned(originData) originData.key = 'some new value' console.log(cloned.value.key) // 'some new value' -stop() ``` ## Manual cloning diff --git a/packages/core/useCloned/index.ts b/packages/core/useCloned/index.ts index f5e368ee283..ab92f50d438 100644 --- a/packages/core/useCloned/index.ts +++ b/packages/core/useCloned/index.ts @@ -1,5 +1,5 @@ import type { MaybeRef } from '@vueuse/shared' -import type { Ref, WatchOptions, WatchStopHandle } from 'vue-demi' +import type { WatchOptions } from 'vue-demi' import { isRef, ref, unref, watch } from 'vue-demi' export interface UseClonedOptions = Record> { @@ -19,18 +19,13 @@ export interface UseClonedOptions = Record> watchOptions?: WatchOptions } -export function useCloned = Record>(source: Ref, options: UseClonedOptions & { manual: true }): { cloned: Ref; sync: () => void } -export function useCloned = Record>(source: Ref, options?: UseClonedOptions & { manual: false } | Omit): { cloned: Ref; sync: () => void; stop: WatchStopHandle } -export function useCloned = Record>(source: T): { cloned: Ref; sync: () => void; stop: WatchStopHandle } export function useCloned = Record>(source: MaybeRef, options: UseClonedOptions = {}) { const cloned = ref({} as T) const { manual, cloneFunction, watchOptions = { immediate: true, deep: true } } = options - let stopWatcher: undefined | WatchStopHandle - if (!manual && isRef(source)) - stopWatcher = watch(source, sync, watchOptions) + watch(source, sync, watchOptions) else sync() @@ -42,5 +37,5 @@ export function useCloned = Record>(source: cloned.value = cloneFunction ? cloneFunction(unref(source), cloned.value) : defaultCloning() } - return { cloned, sync, ...(stopWatcher && { stop: stopWatcher }) } as any + return { cloned, sync } } From 60c91de0a21abff51d0e6e88066ecc46aa4ae05c Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Mon, 15 Aug 2022 17:07:25 +0300 Subject: [PATCH 06/10] feat(useCloned): add structuredClone --- packages/core/useCloned/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/useCloned/index.ts b/packages/core/useCloned/index.ts index ab92f50d438..1f577d89cbe 100644 --- a/packages/core/useCloned/index.ts +++ b/packages/core/useCloned/index.ts @@ -30,7 +30,9 @@ export function useCloned = Record>(source: sync() function defaultCloning() { - return JSON.parse(JSON.stringify(unref(source))) + const structuredClone = window?.structuredClone as any + + return structuredClone ? structuredClone(unref(source)) : JSON.parse(JSON.stringify(unref(source))) } function sync() { From 9ef52cfbfd3d9a78a19ee39edc212ae366f1f204 Mon Sep 17 00:00:00 2001 From: Mikhailov Nikita Date: Tue, 30 Aug 2022 11:00:24 +0300 Subject: [PATCH 07/10] Update packages/core/useCloned/index.md Co-authored-by: Anthony Fu --- packages/core/useCloned/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/useCloned/index.md b/packages/core/useCloned/index.md index 5369d5057fc..d037fe629b7 100644 --- a/packages/core/useCloned/index.md +++ b/packages/core/useCloned/index.md @@ -4,7 +4,7 @@ category: Utilities # useCloned -Reactive partial clone. +Reactive clone of a ref. ## Usage ```ts From 61fe4ff8e7b110e664c7f1c8f73e394cf633b7dc Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Tue, 30 Aug 2022 11:11:58 +0300 Subject: [PATCH 08/10] refactor(useCloned): refactor types and removing watch options nesting --- packages/core/useCloned/index.md | 18 ------------------ packages/core/useCloned/index.test.ts | 2 +- packages/core/useCloned/index.ts | 18 +++++------------- 3 files changed, 6 insertions(+), 32 deletions(-) diff --git a/packages/core/useCloned/index.md b/packages/core/useCloned/index.md index 5369d5057fc..524bcff3f8f 100644 --- a/packages/core/useCloned/index.md +++ b/packages/core/useCloned/index.md @@ -40,21 +40,3 @@ sync() console.log(cloned.value.key)// 'manual' ``` - -## Custom clone function usage -```ts -import { useCloned } from '@vueuse/core/useCloned' - -const data = ref({ - key: 'value' -}) - -const { cloned, sync } = useCloned(data, { - cloneFunction: (source, cloned) => ({ ...source, isCloned: true }) -}) - -data.value.key = 'clone it' - -console.log(cloned.value.isCloned) // true -console.log(cloned.value.key) // 'clone it' -``` diff --git a/packages/core/useCloned/index.test.ts b/packages/core/useCloned/index.test.ts index ec680cb87ad..e5f892d78bd 100644 --- a/packages/core/useCloned/index.test.ts +++ b/packages/core/useCloned/index.test.ts @@ -62,7 +62,7 @@ describe('useCloned', () => { it('works with watch options', async () => { const data = ref({ test: 'test' }) - const { cloned } = useCloned(data, { watchOptions: { immediate: false, deep: false } }) + const { cloned } = useCloned(data, {}, { immediate: false, deep: false }) await nextTick() diff --git a/packages/core/useCloned/index.ts b/packages/core/useCloned/index.ts index 1f577d89cbe..7fadac72786 100644 --- a/packages/core/useCloned/index.ts +++ b/packages/core/useCloned/index.ts @@ -1,8 +1,8 @@ -import type { MaybeRef } from '@vueuse/shared' +import type { MaybeComputedRef } from '@vueuse/shared' import type { WatchOptions } from 'vue-demi' import { isRef, ref, unref, watch } from 'vue-demi' -export interface UseClonedOptions = Record> { +export interface UseClonedOptions { /** * sync source only by function */ @@ -11,18 +11,12 @@ export interface UseClonedOptions = Record> * Custom clone function should return new value for cloned data */ cloneFunction?: (source: T, cloned: T) => T - /** - * Options for watcher - * - * @default { immediate: true, deep: true } - */ - watchOptions?: WatchOptions } -export function useCloned = Record>(source: MaybeRef, options: UseClonedOptions = {}) { +export function useCloned(source: MaybeComputedRef, options: UseClonedOptions = {}, watchOptions: WatchOptions = { deep: true, immediate: true }) { const cloned = ref({} as T) - const { manual, cloneFunction, watchOptions = { immediate: true, deep: true } } = options + const { manual, cloneFunction } = options if (!manual && isRef(source)) watch(source, sync, watchOptions) @@ -30,9 +24,7 @@ export function useCloned = Record>(source: sync() function defaultCloning() { - const structuredClone = window?.structuredClone as any - - return structuredClone ? structuredClone(unref(source)) : JSON.parse(JSON.stringify(unref(source))) + return JSON.parse(JSON.stringify(unref(source))) } function sync() { From 05d551febb5bd135dc485d63ed2afd154654394a Mon Sep 17 00:00:00 2001 From: Mikhailov Nikita Date: Wed, 31 Aug 2022 15:29:23 +0300 Subject: [PATCH 09/10] Update packages/core/useCloned/index.md Co-authored-by: Anthony Fu --- packages/core/useCloned/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/useCloned/index.md b/packages/core/useCloned/index.md index 279a01d9f54..b0123727da5 100644 --- a/packages/core/useCloned/index.md +++ b/packages/core/useCloned/index.md @@ -8,7 +8,7 @@ Reactive clone of a ref. ## Usage ```ts -import { useCloned } from '@vueuse/core/useCloned' +import { useCloned } from '@vueuse/core' const originData = ref({ key: 'value' From 3dead33a883935a39c504f4972a1a43aa1d93a0f Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 5 Sep 2022 21:22:18 +0800 Subject: [PATCH 10/10] chore: update --- packages/core/useCloned/index.md | 37 ++++++++++------ packages/core/useCloned/index.test.ts | 11 +++-- packages/core/useCloned/index.ts | 64 ++++++++++++++++++++------- 3 files changed, 76 insertions(+), 36 deletions(-) diff --git a/packages/core/useCloned/index.md b/packages/core/useCloned/index.md index b0123727da5..71e4b1cdd22 100644 --- a/packages/core/useCloned/index.md +++ b/packages/core/useCloned/index.md @@ -4,35 +4,32 @@ category: Utilities # useCloned -Reactive clone of a ref. +Reactive clone of a ref. By default, it use `JSON.parse(JSON.stringify())` to do the clone. ## Usage + ```ts import { useCloned } from '@vueuse/core' -const originData = ref({ - key: 'value' -}) +const original = ref({ key: 'value' }) -const { cloned } = useCloned(originData) +const { cloned } = useCloned(original) -originData.key = 'some new value' +original.key = 'some new value' console.log(cloned.value.key) // 'some new value' - ``` ## Manual cloning + ```ts -import { useCloned } from '@vueuse/core/useCloned' +import { useCloned } from '@vueuse/core' -const data = ref({ - key: 'value' -}) +const original = ref({ key: 'value' }) -const { cloned, sync } = useCloned(data, { manual: true }) +const { cloned, sync } = useCloned(original, { manual: true }) -data.key = 'manual' +original.key = 'manual' console.log(cloned.value.key) // 'value' @@ -40,3 +37,17 @@ sync() console.log(cloned.value.key)// 'manual' ``` + +## Custom Clone Function + +Using [`klona`](https://www.npmjs.com/package/klona) for example: + +```ts +import { useCloned } from '@vueuse/core' +import { klona } from 'klona' + +const original = ref({ key: 'value' }) + +const { cloned, sync } = useCloned(original, { clone: klona }) +``` + diff --git a/packages/core/useCloned/index.test.ts b/packages/core/useCloned/index.test.ts index e5f892d78bd..dfb299f6539 100644 --- a/packages/core/useCloned/index.test.ts +++ b/packages/core/useCloned/index.test.ts @@ -44,17 +44,16 @@ describe('useCloned', () => { }) it('works with custom clone function', async () => { - const data = ref({ test: 'test' }) - - const { cloned } = useCloned>(data, { cloneFunction: (source, cloned) => ({ ...cloned, ...source, proxyTest: true }) }) + const data = ref>({ test: 'test' }) - cloned.value.check = 'value' + const { cloned } = useCloned(data, { + clone: source => ({ ...source, proxyTest: true }), + }) data.value.test = 'partial' await nextTick() - expect(cloned.value.check).toBe('value') expect(cloned.value.test).toBe('partial') expect(cloned.value.proxyTest).toBe(true) }) @@ -62,7 +61,7 @@ describe('useCloned', () => { it('works with watch options', async () => { const data = ref({ test: 'test' }) - const { cloned } = useCloned(data, {}, { immediate: false, deep: false }) + const { cloned } = useCloned(data, { immediate: false, deep: false }) await nextTick() diff --git a/packages/core/useCloned/index.ts b/packages/core/useCloned/index.ts index 7fadac72786..7c95cacd01d 100644 --- a/packages/core/useCloned/index.ts +++ b/packages/core/useCloned/index.ts @@ -1,34 +1,64 @@ import type { MaybeComputedRef } from '@vueuse/shared' -import type { WatchOptions } from 'vue-demi' +import type { ComputedRef, WatchOptions } from 'vue-demi' import { isRef, ref, unref, watch } from 'vue-demi' -export interface UseClonedOptions { +export interface UseClonedOptions extends WatchOptions { /** - * sync source only by function + * Custom clone function. + * + * By default, it use `JSON.parse(JSON.stringify(value))` to clone. */ - manual?: boolean + clone?: (source: T) => T + /** - * Custom clone function should return new value for cloned data + * Manually sync the ref + * + * @default false */ - cloneFunction?: (source: T, cloned: T) => T + manual?: boolean } -export function useCloned(source: MaybeComputedRef, options: UseClonedOptions = {}, watchOptions: WatchOptions = { deep: true, immediate: true }) { - const cloned = ref({} as T) +export interface UseClonedReturn { + /** + * Cloned ref + */ + cloned: ComputedRef + /** + * Sync cloned data with source manually + */ + sync: () => void +} - const { manual, cloneFunction } = options +function cloneFnJSON(source: T): T { + return JSON.parse(JSON.stringify(source)) +} - if (!manual && isRef(source)) - watch(source, sync, watchOptions) - else - sync() +export function useCloned( + source: MaybeComputedRef, + options: UseClonedOptions = {}, +) { + const cloned = ref({} as T) + const { + manual, + clone = cloneFnJSON, + // watch options + deep = true, + immediate = true, + } = options - function defaultCloning() { - return JSON.parse(JSON.stringify(unref(source))) + function sync() { + cloned.value = clone(unref(source)) } - function sync() { - cloned.value = cloneFunction ? cloneFunction(unref(source), cloned.value) : defaultCloning() + if (!manual && isRef(source)) { + watch(source, sync, { + ...options, + deep, + immediate, + }) + } + else { + sync() } return { cloned, sync }