Skip to content

Commit

Permalink
fix(router): improves router functions: synchronize values, adds test…
Browse files Browse the repository at this point in the history
…s, and optimize performance (#3184)

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
Co-authored-by: CJBoy <78361904+cjboy76@users.noreply.github.com>
Co-authored-by: tzyoo <37643993+Tzyito@users.noreply.github.com>
  • Loading branch information
4 people committed Jul 30, 2023
1 parent 6ae3a8c commit 0d02f6c
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 59 deletions.
30 changes: 28 additions & 2 deletions packages/router/useRouteHash/index.test.ts
@@ -1,9 +1,9 @@
import { nextTick } from 'vue-demi'
import { nextTick, reactive, ref } from 'vue-demi'
import { describe, expect, it } from 'vitest'
import { useRouteHash } from '.'

describe('useRouteHash', () => {
const getRoute = (hash?: any) => ({
const getRoute = (hash?: any) => reactive({
query: {},
fullPath: '',
hash,
Expand Down Expand Up @@ -62,4 +62,30 @@ describe('useRouteHash', () => {
expect(hash.value).toBe('baz')
expect(route.hash).toBeUndefined()
})

it('should change the value when the route changes', () => {
let route = getRoute()
const router = { replace: (r: any) => route = r } as any

const hash = useRouteHash('baz', { route, router })

route.hash = 'foo'

expect(hash.value).toBe('foo')
})

it('should allow ref or getter as default value', () => {
let route = getRoute()
const router = { replace: (r: any) => route = r } as any

const defaultTarget = ref('foo')

const target = useRouteHash(defaultTarget, { route, router })

expect(target.value).toBe('foo')

target.value = 'bar'

expect(target.value).toBe('bar')
})
})
51 changes: 37 additions & 14 deletions packages/router/useRouteHash/index.ts
@@ -1,12 +1,13 @@
import { customRef, nextTick } from 'vue-demi'
import { customRef, nextTick, watch } from 'vue-demi'
import { useRoute, useRouter } from 'vue-router'
import { toValue, tryOnScopeDispose } from '@vueuse/shared'
import type { MaybeRefOrGetter } from '@vueuse/shared'
import type { ReactiveRouteOptions, RouteHashValueRaw } from '../_types'

let _hash: RouteHashValueRaw

export function useRouteHash(
defaultValue?: RouteHashValueRaw,
defaultValue?: MaybeRefOrGetter<RouteHashValueRaw>,
{
mode = 'replace',
route = useRoute(),
Expand All @@ -19,20 +20,42 @@ export function useRouteHash(
_hash = undefined
})

return customRef<RouteHashValueRaw>((track, trigger) => ({
get() {
track()
let _trigger: () => void

return _hash || defaultValue
},
set(v) {
_hash = v === null ? undefined : v
const proxy = customRef<RouteHashValueRaw>((track, trigger) => {
_trigger = trigger

return {
get() {
track()

return _hash || toValue(defaultValue)
},
set(v) {
if (v === _hash)
return

_hash = v === null ? undefined : v

trigger()
trigger()

nextTick(() => {
router[toValue(mode)]({ ...route, hash: _hash as string })
})
nextTick(() => {
const { params, query } = route

router[toValue(mode)]({ params, query, hash: _hash as string })
})
},
}
})

watch(
() => route.hash,
() => {
_hash = route.hash
_trigger()
},
}))
{ flush: 'sync' },
)

return proxy
}
75 changes: 72 additions & 3 deletions packages/router/useRouteParams/index.test.ts
@@ -1,10 +1,10 @@
import { effectScope, nextTick, ref } from 'vue-demi'
import { describe, expect, it } from 'vitest'
import { effectScope, nextTick, reactive, ref, watch } from 'vue-demi'
import { describe, expect, it, vi } from 'vitest'
import type { Ref } from 'vue-demi'
import { useRouteParams } from '.'

describe('useRouteParams', () => {
const getRoute = (params: Record<string, any> = {}) => ({
const getRoute = (params: Record<string, any> = {}) => reactive({
params,
query: {},
fullPath: '',
Expand Down Expand Up @@ -143,4 +143,73 @@ describe('useRouteParams', () => {
expect(page.value).toBeNull()
expect(lang.value).toBeNull()
})

it('should change the value when the route changes', () => {
let route = getRoute()
const router = { replace: (r: any) => route = r } as any

const lang: Ref<any> = useRouteParams('lang', null, { route, router })

expect(lang.value).toBeNull()

route.params = { lang: 'en' }

expect(lang.value).toBe('en')
})

it('should avoid trigger effects when the value doesn\'t change', async () => {
let route = getRoute()
const router = { replace: (r: any) => route = r } as any
const onUpdate = vi.fn()

const page = useRouteParams('page', 1, { transform: Number, route, router })

watch(page, onUpdate)

page.value = 1

await nextTick()

expect(page.value).toBe(1)
expect(route.params.page).toBe(1)
expect(onUpdate).not.toHaveBeenCalled()
})

it('should keep current query and hash', async () => {
let route = getRoute()
const router = { replace: (r: any) => route = r } as any

route.query = { foo: 'bar' }
route.hash = '#hash'

const id: Ref<any> = useRouteParams('id', null, { route, router })

id.value = '2'

await nextTick()

expect(id.value).toBe('2')
expect(route.hash).toBe('#hash')
expect(route.query).toEqual({ foo: 'bar' })
})

it('should allow ref or getter as default value', () => {
let route = getRoute()
const router = { replace: (r: any) => route = r } as any

const defaultPage = ref(1)
const defaultLang = () => 'pt-BR'

const page: Ref<any> = useRouteParams('page', defaultPage, { route, router })
const lang: Ref<any> = useRouteParams('lang', defaultLang, { route, router })

expect(page.value).toBe(1)
expect(lang.value).toBe('pt-BR')

page.value = 2
lang.value = 'en-US'

expect(page.value).toBe(2)
expect(lang.value).toBe('en-US')
})
})
67 changes: 49 additions & 18 deletions packages/router/useRouteParams/index.ts
@@ -1,8 +1,9 @@
import type { Ref } from 'vue-demi'
import { customRef, nextTick } from 'vue-demi'
import type { RouteParamValueRaw } from 'vue-router'
import { customRef, nextTick, watch } from 'vue-demi'
import { useRoute, useRouter } from 'vue-router'
import { toValue, tryOnScopeDispose } from '@vueuse/shared'
import type { Ref } from 'vue-demi'
import type { MaybeRefOrGetter } from '@vueuse/shared'
import type { LocationAsRelativeRaw, RouteParamValueRaw } from 'vue-router'
import type { ReactiveRouteOptionsWithTransform } from '../_types'

const _cache = new WeakMap()
Expand All @@ -16,7 +17,7 @@ export function useRouteParams<
K = T,
>(
name: string,
defaultValue?: T,
defaultValue?: MaybeRefOrGetter<T>,
options?: ReactiveRouteOptionsWithTransform<T, K>
): Ref<K>

Expand All @@ -25,7 +26,7 @@ export function useRouteParams<
K = T,
>(
name: string,
defaultValue?: T,
defaultValue?: MaybeRefOrGetter<T>,
options: ReactiveRouteOptionsWithTransform<T, K> = {},
): Ref<K> {
const {
Expand All @@ -46,21 +47,51 @@ export function useRouteParams<

_params.set(name, route.params[name])

return customRef<any>((track, trigger) => ({
get() {
track()
let _trigger: () => void

const data = _params.get(name) ?? defaultValue
return transform(data as T)
},
set(v) {
_params.set(name, (v === defaultValue || v === null) ? undefined : v)
const proxy = customRef<any>((track, trigger) => {
_trigger = trigger

return {
get() {
track()

const data = _params.get(name)

return transform(data !== undefined ? data : toValue(defaultValue))
},
set(v) {
if (_params.get(name) === v)
return

trigger()
_params.set(name, v)

nextTick(() => {
router[toValue(mode)]({ ...route, params: { ...route.params, ...Object.fromEntries(_params.entries()) } })
})
trigger()

nextTick(() => {
const { params, query, hash } = route
router[toValue(mode)]({
params: {
...params,
...Object.fromEntries(_params.entries()),
},
query,
hash,
} as LocationAsRelativeRaw)
})
},
}
})

watch(
() => route.params[name],
(v) => {
_params.set(name, v)

_trigger()
},
}))
{ flush: 'sync' },
)

return proxy as Ref<K>
}

0 comments on commit 0d02f6c

Please sign in to comment.