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(watchArray): new function #1705

Merged
merged 6 commits into from Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
28 changes: 28 additions & 0 deletions packages/shared/watchListChanges/index.md
@@ -0,0 +1,28 @@
---
category: Watch
---

# watchListChange
antfu marked this conversation as resolved.
Show resolved Hide resolved

`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.
w1ndy marked this conversation as resolved.
Show resolved Hide resolved

```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]
})
```
64 changes: 64 additions & 0 deletions 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)
antfu marked this conversation as resolved.
Show resolved Hide resolved
})
})
27 changes: 27 additions & 0 deletions 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<V = any, OV = any> = (value: V, oldValue: OV, added: V, removed: OV, onCleanup: (cleanupFn: () => void) => void) => any

export function watchListChanges<T, Immediate extends Readonly<boolean> = false>(source: WatchSource<T[]>, cb: WatchListCallback<T[], Immediate extends true ? T[] | undefined : T[]>, options?: WatchOptions<Immediate>) {
let oldList: T[] = options?.immediate ? [] : [...(source instanceof Function ? source() : unref(source))]
watch(source, (newList, _, onCleanup) => {
const oldListRemains = new Array<boolean>(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)
}