+<script lang="ts" setup>
+import { inject, computed, ref } from 'vue'
+import { useLink, useRoute } from 'vue-router'
+import AppLink from './AppLink.vue'
+import SimpleView from './SimpleView.vue'
+
+const route = useRoute()
+const state = inject('state')
+
+useLink({ to: '/' })
+useLink({ to: '/documents/hello' })
+useLink({ to: '/children' })
+
+const currentLocation = computed(() => {
+ const { matched, ...rest } = route
+ return rest
+})
+
+const nextUserLink = computed(
+ () => '/users/' + String((Number(route.params.id) || 0) + 1)
+)
+
+const simple = ref(false)
+</script>
+
<template>
<div>
<pre>{{ currentLocation }}</pre>
<input type="checkbox" v-model="state.cancelNextNavigation" /> Cancel Next
Navigation
</label>
+
+ <label>
+ <input type="checkbox" v-model="simple" /> Use Simple RouterView
+ </label>
+
<ul>
<li>
<router-link to="/n/%E2%82%AC">/n/%E2%82%AC</router-link>
<li>
<router-link to="/p_1/absolute-a">/p_1/absolute-a</router-link>
</li>
+ <li>
+ <RouterLink to="/rerender" v-slot="{ href }">{{ href }}</RouterLink>
+ </li>
+ <li>
+ <RouterLink to="/rerender/a" v-slot="{ href }">{{ href }}</RouterLink>
+ </li>
+ <li>
+ <RouterLink to="/rerender/b" v-slot="{ href }">{{ href }}</RouterLink>
+ </li>
</ul>
<button @click="toggleViewName">Toggle view</button>
- <RouterView :name="viewName" v-slot="{ Component, route }">
- <Transition
- :name="route.meta.transition || 'fade'"
- mode="out-in"
- @before-enter="flushWaiter"
- @before-leave="setupWaiter"
- >
- <!-- <KeepAlive> -->
- <Suspense>
- <template #default>
- <component
- :is="Component"
- :key="route.name === 'repeat' ? route.path : route.meta.key"
- />
- </template>
- <template #fallback> Loading... </template>
- </Suspense>
- <!-- </KeepAlive> -->
- </Transition>
- </RouterView>
+
+ <SimpleView :simple="simple"></SimpleView>
</div>
</template>
-
-<script lang="ts">
-import { defineComponent, inject, computed, ref } from 'vue'
-import { scrollWaiter } from './scrollWaiter'
-import { useLink, useRoute } from 'vue-router'
-import AppLink from './AppLink.vue'
-
-export default defineComponent({
- name: 'App',
- components: { AppLink },
- setup() {
- const route = useRoute()
- const state = inject('state')
- const viewName = ref('default')
-
- useLink({ to: '/' })
- useLink({ to: '/documents/hello' })
- useLink({ to: '/children' })
-
- const currentLocation = computed(() => {
- const { matched, ...rest } = route
- return rest
- })
-
- function flushWaiter() {
- scrollWaiter.flush()
- }
- function setupWaiter() {
- scrollWaiter.add()
- }
-
- const nextUserLink = computed(
- () => '/users/' + String((Number(route.params.id) || 0) + 1)
- )
-
- return {
- currentLocation,
- nextUserLink,
- state,
- flushWaiter,
- setupWaiter,
- viewName,
- toggleViewName() {
- viewName.value = viewName.value === 'default' ? 'other' : 'default'
- },
- }
- },
-})
-</script>
--- /dev/null
+<script lang="ts" setup>
+import { ref } from 'vue'
+import { scrollWaiter } from './scrollWaiter'
+
+defineProps<{ simple: boolean }>()
+const viewName = ref('default')
+
+function flushWaiter() {
+ scrollWaiter.flush()
+}
+function setupWaiter() {
+ scrollWaiter.add()
+}
+</script>
+
+<template>
+ <RouterView v-if="simple" v-slot="{ Component, route }">
+ <component :is="Component" :key="route.meta.key" />
+ </RouterView>
+
+ <RouterView
+ v-else
+ :name="viewName"
+ v-slot="{ Component, route }"
+ key="not-simple"
+ >
+ <Transition
+ :name="route.meta.transition || 'fade'"
+ mode="out-in"
+ @before-enter="flushWaiter"
+ @before-leave="setupWaiter"
+ >
+ <!-- <KeepAlive> -->
+ <!-- <Suspense>
+ <template #default> -->
+ <!-- <div v-if="route.path.endsWith('/a')">A</div>
+ <div v-else>B</div> -->
+ <component
+ :is="Component"
+ :key="route.name === 'repeat' ? route.path : route.meta.key"
+ />
+ <!-- </template>
+ <template #fallback> Loading... </template>
+ </Suspense> -->
+ <!-- </KeepAlive> -->
+ </Transition>
+ </RouterView>
+</template>
import { globalState } from './store'
import { scrollWaiter } from './scrollWaiter'
import RepeatedParams from './views/RepeatedParams.vue'
+import RerenderCheck from './views/RerenderCheck.vue'
+import { h } from 'vue'
+
let removeRoute: (() => void) | undefined
export const routerHistory = createWebHistory()
{ path: 'settings', component },
],
},
+
+ {
+ path: '/rerender',
+ component: RerenderCheck,
+ children: [
+ { path: 'a', component: { render: () => h('div', 'Child A') } },
+ { path: 'b', component: { render: () => h('div', 'Child B') } },
+ ],
+ },
],
async scrollBehavior(to, from, savedPosition) {
await scrollWaiter.wait()
--- /dev/null
+<script lang="ts" setup>
+import { onUpdated } from 'vue'
+let count = 0
+onUpdated(() => {
+ console.log(`RerenderCheck.vue render: ${++count}`)
+})
+</script>
+
+<template>
+ <RouterView key="fixed" />
+</template>
{ flush: 'post' }
)
+ let matchedRoute: RouteLocationMatched | undefined
+ let currentName: string
+ // Since in Vue the entering view mounts first and then the leaving unmounts,
+ // we need to keep track of the last route in order to use it in the unmounted
+ // event
+ let lastMatchedRoute: RouteLocationMatched | undefined
+ let lastCurrentName: string
+
+ const onVnodeUnmounted: VNodeProps['onVnodeUnmounted'] = vnode => {
+ // remove the instance reference to prevent leak
+ if (lastMatchedRoute && vnode.component!.isUnmounted) {
+ lastMatchedRoute.instances[lastCurrentName] = null
+ }
+ }
+
return () => {
const route = routeToDisplay.value
+ lastMatchedRoute = matchedRoute
+ lastCurrentName = currentName
// we need the value at the time we render because when we unmount, we
// navigated to a different location so the value is different
- const currentName = props.name
- const matchedRoute = matchedRouteRef.value
+ currentName = props.name
+ matchedRoute = matchedRouteRef.value
+
const ViewComponent =
matchedRoute && matchedRoute.components![currentName]
}
// props from route configuration
- const routePropsOption = matchedRoute.props[currentName]
+ // matchedRoute exists since we check with if (ViewComponent)
+ const routePropsOption = matchedRoute!.props[currentName]
const routeProps = routePropsOption
? routePropsOption === true
? route.params
: routePropsOption
: null
- const onVnodeUnmounted: VNodeProps['onVnodeUnmounted'] = vnode => {
- // remove the instance reference to prevent leak
- if (vnode.component!.isUnmounted) {
- matchedRoute.instances[currentName] = null
- }
- }
-
const component = h(
ViewComponent,
assign({}, routeProps, attrs, {
// TODO: can display if it's an alias, its props
const info: RouterViewDevtoolsContext = {
depth: depth.value,
- name: matchedRoute.name,
- path: matchedRoute.path,
- meta: matchedRoute.meta,
+ // same as above: ensured with if (ViewComponent) above
+ name: matchedRoute!.name,
+ path: matchedRoute!.path,
+ meta: matchedRoute!.meta,
}
const internalInstances = isArray(component.ref)