Skip to content

Commit

Permalink
feat(useSorted): new function (#1799)
Browse files Browse the repository at this point in the history
  • Loading branch information
okxiaoliang4 committed Oct 16, 2022
1 parent e08f758 commit feaa195
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/core/index.ts
Expand Up @@ -96,6 +96,7 @@ export * from './useScroll'
export * from './useScrollLock'
export * from './useSessionStorage'
export * from './useShare'
export * from './useSorted'
export * from './useSpeechRecognition'
export * from './useSpeechSynthesis'
export * from './useStepper'
Expand Down
58 changes: 58 additions & 0 deletions packages/core/useSorted/demo.vue
@@ -0,0 +1,58 @@
<script setup lang="ts">
import { rand } from '@vueuse/shared'
import { computed, ref } from 'vue'
import { useSorted } from '.'
const objArr = [{
name: 'John',
age: 40,
}, {
name: 'Jane',
age: 20,
}, {
name: 'Joe',
age: 30,
}, {
name: 'Jenny',
age: 22,
}]
const result2 = useSorted(objArr, (a, b) => a.age - b.age)
const arrText = ref('')
const inputArr = computed(() => arrText.value.split(','))
const inputOut = useSorted(inputArr)
function randomArr() {
const arr = []
for (let i = 0; i < rand(10, 20); i++)
arr.push(rand(0, 100))
arrText.value = arr.join(',')
}
randomArr()
</script>

<template>
<div>
<div class="flex items-center">
input:
<input v-model="arrText" type="text">
</div>
<div>
<button @click="randomArr">
random
</button>
</div>
output: {{ inputOut }}
</div>

<div class="mt-10">
<div>object property sort:</div>
<div>input:</div>
<div>{{ objArr }}</div>
<div class="mt-5">
output:
</div>
<div>{{ result2 }}</div>
</div>
</template>
45 changes: 45 additions & 0 deletions packages/core/useSorted/index.md
@@ -0,0 +1,45 @@
---
category: Array
---

# useSorted

reactive sort array

## Usage

```ts
import { useSorted } from '@vueuse/core'

// general sort
const source = [10, 3, 5, 7, 2, 1, 8, 6, 9, 4]
const sorted = useSorted(source)
console.log(sorted.value) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(source) // [10, 3, 5, 7, 2, 1, 8, 6, 9, 4]

// object sort
const objArr = [{
name: 'John',
age: 40,
}, {
name: 'Jane',
age: 20,
}, {
name: 'Joe',
age: 30,
}, {
name: 'Jenny',
age: 22,
}]
const objSorted = useSorted(objArr, (a, b) => a.age - b.age)
```
### dirty mode

dirty mode will change the source array.
```ts
const source = ref([10, 3, 5, 7, 2, 1, 8, 6, 9, 4])
const sorted = useSorted(source, (a, b) => a - b, {
dirty: true,
})
console.log(source)// output: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
```
94 changes: 94 additions & 0 deletions packages/core/useSorted/index.test.ts
@@ -0,0 +1,94 @@
import { unref } from 'vue'
import { useSorted } from '.'

interface User {
name: string
age: number
}

const arr = [10, 3, 5, 7, 2, 1, 8, 6, 9, 4]
const arrSorted = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

const objArr: User[] = [
{
name: 'John',
age: 40,
},
{
name: 'Jane',
age: 20,
},
{
name: 'Joe',
age: 30,
},
{
name: 'Jenny',
age: 22,
},
]
const objectSorted: User[] = [
{
name: 'Jane',
age: 20,
},
{
name: 'Jenny',
age: 22,
},
{
name: 'Joe',
age: 30,
},
{
name: 'John',
age: 40,
},
]

describe('useSorted', () => {
it('should be defined', () => {
expect(useSorted).toBeDefined()
})

it('should pure sort function', () => {
const sorted = useSorted(arr)
expect(unref(sorted)).toMatchObject(arrSorted)
expect(unref(arr)).toMatchInlineSnapshot(`
[
10,
3,
5,
7,
2,
1,
8,
6,
9,
4,
]
`)
})

it('should dirty sort', () => {
const dirtyArr = [...arr]
const sorted = useSorted(dirtyArr, (a, b) => a - b, { dirty: true })

expect(unref(sorted)).toMatchObject(arrSorted)
expect(unref(dirtyArr)).toMatchObject(unref(sorted))
})

it('should sort object', () => {
const sorted = useSorted(objArr, (a, b) => a.age - b.age)

expect(unref(sorted)).toMatchObject(objectSorted)
})

it('should sort object by options.compareFn', () => {
const sorted = useSorted(objArr, {
compareFn: (a, b) => a.age - b.age,
})

expect(unref(sorted)).toMatchObject(objectSorted)
})
})
75 changes: 75 additions & 0 deletions packages/core/useSorted/index.ts
@@ -0,0 +1,75 @@
import type { Ref } from 'vue-demi'
import type { MaybeRef } from '@vueuse/shared'
import { computed, isRef, unref, watchEffect } from 'vue-demi'

export type UseSortedCompareFn<T = any> = (a: T, b: T) => number

export type UseSortedFn<T = any> = (arr: T[], compareFn: UseSortedCompareFn<T>) => T[]

export interface UseSortedOptions<T = any> {
/**
* sort algorithm
*/
sortFn?: UseSortedFn<T>
/**
* compare function
*/
compareFn?: UseSortedCompareFn<T>
/**
* change the value of the source array
* @default false
*/
dirty?: boolean
}

const defaultSortFn: UseSortedFn = <T>(source: T[], compareFn: UseSortedCompareFn<T>): T[] => source.sort(compareFn)
const defaultCompare: UseSortedCompareFn<number> = (a, b) => a - b

export function useSorted<T = any>(source: MaybeRef<T[]>, compareFn?: UseSortedCompareFn<T>): Ref<T[]>
export function useSorted<T = any>(source: MaybeRef<T[]>, options?: UseSortedOptions<T>): Ref<T[]>
export function useSorted<T = any>(source: MaybeRef<T[]>, compareFn?: UseSortedCompareFn<T>, options?: Omit<UseSortedOptions<T>, 'compareFn'>): Ref<T[]>
/**
* reactive sort array
*
* @see https://vueuse.org/useSorted
* @param source source array
* @param options
*/
export function useSorted(...args: any[]) {
const [source] = args
let compareFn: UseSortedCompareFn = defaultCompare
let options: UseSortedOptions = {}

if (args.length === 2) {
if (typeof args[1] === 'object') {
options = args[1]
compareFn = options.compareFn ?? defaultCompare
}
else {
compareFn = args[1] ?? defaultCompare
}
}
else if (args.length > 2) {
compareFn = args[1] ?? defaultCompare
options = args[2] ?? {}
}

const {
dirty = false,
sortFn = defaultSortFn,
} = options

if (!dirty)
return computed(() => sortFn([...unref(source)], compareFn))

// dirty
watchEffect(() => {
const result = sortFn(unref(source), compareFn)
if (isRef(source))
source.value = result
else
source.splice(0, source.length, ...result)
})

return source
}

0 comments on commit feaa195

Please sign in to comment.