Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(watchArray): new function (#1705)
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
- Loading branch information
Showing
3 changed files
with
168 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
--- | ||
category: Watch | ||
--- | ||
|
||
# watchArray | ||
|
||
Watch for an array with additions and removals. | ||
|
||
## 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 { watchArray } from '@vueuse/core' | ||
|
||
const list = ref([1, 2, 3]) | ||
|
||
watchArray(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] | ||
}) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { isVue2, nextTick, reactive, ref } from 'vue-demi' | ||
import { watchArray } from '.' | ||
|
||
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]) | ||
expect(oldList).toEqual([1, 2, 3]) | ||
expect(added).toEqual([1, 4]) | ||
expect(removed).toEqual([2, 3]) | ||
}) | ||
|
||
const num = ref([1, 2, 3]) | ||
watchArray(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]) | ||
watchArray(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]) | ||
watchArray(num, spy, { deep: true }) | ||
num.value.push(4) | ||
await nextTick() | ||
// TODO: Vue 2 somehow get trigger twice | ||
expect(spy).toBeCalledTimes(isVue2 ? 2 : 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]) | ||
watchArray(num, spy, { deep: true }) | ||
num.value.splice(1, 1, 5, 6, 7) | ||
await nextTick() | ||
// TODO: Vue 2 somehow get trigger twice | ||
expect(spy).toBeCalledTimes(isVue2 ? 2 : 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) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import type { WatchOptions, WatchSource } from 'vue-demi' | ||
import { unref, watch } from 'vue-demi' | ||
|
||
export declare type WatchArrayCallback<V = any, OV = any> = (value: V, oldValue: OV, added: V, removed: OV, onCleanup: (cleanupFn: () => void) => void) => any | ||
|
||
/** | ||
* Watch for an array with additions and removals. | ||
* | ||
* @see https://vueuse.org/watchArray | ||
*/ | ||
export function watchArray<T, Immediate extends Readonly<boolean> = false>( | ||
source: WatchSource<T[]> | T[], | ||
cb: WatchArrayCallback<T[], Immediate extends true ? T[] | undefined : T[]>, | ||
options?: WatchOptions<Immediate>, | ||
) { | ||
let oldList: T[] = options?.immediate | ||
? [] | ||
: [...(source instanceof Function | ||
? source() | ||
: Array.isArray(source) | ||
? source | ||
: unref(source)), | ||
] | ||
|
||
return watch(source as WatchSource<T[]>, (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) | ||
} |