params: {},
hash: '',
meta: {},
- matched: [{ components: { default: components.Home }, path: '/' }],
+ matched: [
+ { components: { default: components.Home }, instances: {}, path: '/' },
+ ],
},
foo: {
fullPath: '/foo',
params: {},
hash: '',
meta: {},
- matched: [{ components: { default: components.Foo }, path: '/foo' }],
+ matched: [
+ { components: { default: components.Foo }, instances: {}, path: '/foo' },
+ ],
},
nested: {
fullPath: '/a',
hash: '',
meta: {},
matched: [
- { components: { default: components.Nested }, path: '/' },
- { components: { default: components.Foo }, path: 'a' },
+ { components: { default: components.Nested }, instances: {}, path: '/' },
+ { components: { default: components.Foo }, instances: {}, path: 'a' },
],
},
nestedNested: {
hash: '',
meta: {},
matched: [
- { components: { default: components.Nested }, path: '/' },
- { components: { default: components.Nested }, path: 'a' },
- { components: { default: components.Foo }, path: 'b' },
+ { components: { default: components.Nested }, instances: {}, path: '/' },
+ { components: { default: components.Nested }, instances: {}, path: 'a' },
+ { components: { default: components.Foo }, instances: {}, path: 'b' },
],
},
named: {
params: {},
hash: '',
meta: {},
- matched: [{ components: { foo: components.Foo }, path: '/' }],
+ matched: [
+ { components: { foo: components.Foo }, instances: {}, path: '/' },
+ ],
},
withParams: {
fullPath: '/users/1',
matched: [
{
components: { default: components.User },
+
+ instances: {},
path: '/users/:id',
props: true,
},
matched: [
{
components: { default: components.WithProps },
+
+ instances: {},
path: '/props/:id',
props: { id: 'foo', other: 'fixed' },
},
matched: [
{
components: { default: components.WithProps },
+
+ instances: {},
path: '/props/:id',
props: to => ({ id: Number(to.params.id) * 2, other: to.query.q }),
},
function factory(route: RouteLocationNormalizedLoose, props: any = {}) {
const router = {
- currentRoute: ref(markNonReactive({ ...route })),
+ currentRoute: ref(
+ markNonReactive({
+ ...route,
+ // reset the instances everytime
+ matched: route.matched.map(match => ({ ...match, instances: {} })),
+ })
+ ),
}
const { app, el } = mount(
await p.catch(err => {}) // catch the navigation abortion
expect(currentRoute.fullPath).toBe('/guard')
})
+
+ it.todo('invokes with the component context')
+ it.todo('invokes with the component context with named views')
+ it.todo('invokes with the component context with nested views')
+ it.todo('invokes with the component context with nested named views')
})
})
})
await p
expect(router.currentRoute.value.fullPath).toBe('/guard/foo')
})
+
+ it.todo('invokes with the component context')
+ it.todo('invokes with the component context with named views')
+ it.todo('invokes with the component context with nested views')
+ it.todo('invokes with the component context with nested named views')
})
})
})
aliasOf: undefined,
components: { default: {} },
leaveGuards: [],
+ instances: {},
meta: {},
name: undefined,
path: '/home',
children: [{ path: '/child' }],
components: { default: {} },
leaveGuards: [],
+ instances: {},
meta: { foo: true },
name: 'name',
path: '/home',
aliasOf: undefined,
components: {},
leaveGuards: [],
+ instances: {},
meta: { foo: true },
name: 'name',
path: '/redirect',
children: [{ path: '/child' }],
components: { one: {} },
leaveGuards: [],
+ instances: {},
meta: { foo: true },
name: 'name',
path: '/home',
aliasOf: undefined,
components: {},
leaveGuards: [],
+ instances: {},
meta: {},
name: undefined,
path: '/redirect',
'path' | 'name' | 'components' | 'children' | 'meta' | 'beforeEnter'
> {
leaveGuards?: any
+ instances: Record<string, any>
props?: RouteRecordCommon['props']
aliasOf: RouteRecordViewLoose | undefined
}
redirectedFrom?: Partial<MatcherLocationNormalized>
meta: any
matched: Partial<RouteRecordViewLoose>[]
+ instances: Record<string, any>
}
declare global {
export default defineComponent({
name: 'ComponentWithData',
async setup() {
- const data = reactive({ other: null })
+ const data = reactive({ other: 'old' })
data.fromApi = await getData()
// TODO: add sample with onBeforeRouteUpdate()
PropType,
computed,
InjectionKey,
- Ref,
+ ref,
+ ComponentPublicInstance,
+ ComputedRef,
} from 'vue'
-import { RouteRecordNormalized } from '../matcher/types'
import { routeKey } from '../injectKeys'
-import { RouteComponent } from '../types'
+import { RouteLocationMatched } from '../types'
// TODO: make it work with no symbols too for IE
export const matchedRouteKey = Symbol() as InjectionKey<
- Ref<RouteRecordNormalized>
+ ComputedRef<RouteLocationMatched | undefined>
>
export const View = defineComponent({
const depth: number = inject('routerViewDepth', 0)
provide('routerViewDepth', depth + 1)
- const matchedRoute = computed(() => route.value.matched[depth])
- const ViewComponent = computed<RouteComponent | undefined>(
+ const matchedRoute = computed(
+ () => route.value.matched[depth] as RouteLocationMatched | undefined
+ )
+ const ViewComponent = computed(
() => matchedRoute.value && matchedRoute.value.components[props.name]
)
const propsData = computed(() => {
- const { props } = matchedRoute.value
+ // propsData only gets called if ViewComponent.value exists and it depends on matchedRoute.value
+ const { props } = matchedRoute.value!
if (!props) return {}
if (props === true) return route.value.params
provide(matchedRouteKey, matchedRoute)
+ const viewRef = ref<ComponentPublicInstance>()
+
+ function onVnodeMounted() {
+ // if we mount, there is a matched record
+ matchedRoute.value!.instances[props.name] = viewRef.value
+ // TODO: trigger beforeRouteEnter hooks
+ }
+
return () => {
return ViewComponent.value
- ? h(ViewComponent.value as any, { ...propsData.value, ...attrs })
+ ? h(ViewComponent.value as any, {
+ ...propsData.value,
+ ...attrs,
+ onVnodeMounted,
+ ref: viewRef,
+ })
: null
}
},
props: record.props || false,
meta: record.meta || {},
leaveGuards: [],
+ instances: {},
aliasOf: undefined,
}
}
meta: Exclude<RouteRecordMultipleViews['meta'], void>
props: Exclude<RouteRecordCommon['props'], void>
beforeEnter: RouteRecordMultipleViews['beforeEnter']
- leaveGuards: NavigationGuard[]
+ leaveGuards: NavigationGuard<undefined>[]
+ // TODO: should be ComponentPublicInstance but breaks Immutable type
+ instances: Record<string, {} | undefined | null>
aliasOf: RouteRecordNormalized | undefined
}
push(to: RouteLocation): Promise<RouteLocationNormalizedResolved>
replace(to: RouteLocation): Promise<RouteLocationNormalizedResolved>
- beforeEach(guard: NavigationGuard): ListenerRemover
+ beforeEach(guard: NavigationGuard<undefined>): ListenerRemover
afterEach(guard: PostNavigationGuard): ListenerRemover
onError(handler: ErrorHandler): ListenerRemover
}: RouterOptions): Router {
const matcher = createRouterMatcher(routes, {})
- const beforeGuards = useCallbacks<NavigationGuard>()
+ const beforeGuards = useCallbacks<NavigationGuard<undefined>>()
const afterGuards = useCallbacks<PostNavigationGuard>()
const currentRoute = ref<RouteLocationNormalizedResolved>(
START_LOCATION_NORMALIZED
for (const guard of record.leaveGuards) {
guards.push(guardToPromiseFn(guard, to, from))
}
+
+ // free the references
+ record.instances = {}
}
// run the queue of per route beforeRouteLeave guards
import { LocationQuery, LocationQueryRaw } from '../utils/query'
import { PathParserOptions } from '../matcher/path-parser-ranker'
-import { markNonReactive, ComponentOptions } from 'vue'
+import { markNonReactive, ComponentOptions, ComponentPublicInstance } from 'vue'
import { RouteRecordNormalized } from '../matcher/types'
export type Lazy<T> = () => Promise<T>
// }
// TODO: type this for beforeRouteUpdate and beforeRouteLeave
+// TODO: support arrays
export interface RouteComponentInterface {
- beforeRouteEnter?: NavigationGuard<void>
+ beforeRouteEnter?: NavigationGuard<undefined>
/**
* Guard called when the router is navigating away from the current route
* that is rendering this component.
* @param from RouteLocation we are navigating from
* @param next function to validate, cancel or modify (by redirectering) the navigation
*/
- beforeRouteLeave?: NavigationGuard<void>
+ beforeRouteLeave?: NavigationGuard
/**
* Guard called whenever the route that renders this component has changed but
* it is reused for the new route. This allows you to guard for changes in params,
* @param from RouteLocation we are navigating from
* @param next function to validate, cancel or modify (by redirectering) the navigation
*/
- beforeRouteUpdate?: NavigationGuard<void>
+ beforeRouteUpdate?: NavigationGuard
}
// TODO: allow defineComponent export type RouteComponent = (Component | ReturnType<typeof defineComponent>) &
| Record<string, any>
| ((to: RouteLocationNormalized) => Record<string, any>)
// TODO: beforeEnter has no effect with redirect, move and test
- beforeEnter?: NavigationGuard | NavigationGuard[]
+ beforeEnter?: NavigationGuard<undefined> | NavigationGuard<undefined>[]
meta?: Record<string | number | symbol, any>
// TODO: only allow a subset?
// TODO: RFC: remove this and only allow global options
export type NavigationGuardNextCallback = (vm: any) => any
-export interface NavigationGuard<V = void> {
+export interface NavigationGuard<V = ComponentPublicInstance> {
(
this: V,
// TODO: we could maybe add extra information like replace: true/false
NavigationError,
NavigationRedirectError,
} from '../errors'
+import { ComponentPublicInstance } from 'vue'
export function guardToPromiseFn(
- guard: NavigationGuard,
+ guard: NavigationGuard<undefined>,
to: RouteLocationNormalized,
- from: RouteLocationNormalizedResolved
- // record?: RouteRecordNormalized
+ from: RouteLocationNormalizedResolved,
+ instance?: undefined
+): () => Promise<void>
+export function guardToPromiseFn<
+ ThisType extends ComponentPublicInstance | undefined
+>(
+ guard: NavigationGuard<ThisType>,
+ to: RouteLocationNormalized,
+ from: RouteLocationNormalizedResolved,
+ instance: ThisType
): () => Promise<void> {
return () =>
new Promise((resolve, reject) => {
}
}
- guard(to, from, next)
+ guard.call(instance, to, from, next)
})
}
// replace the function with the resolved component
record.components[name] = resolvedComponent
const guard = resolvedComponent[guardType]
- return guard && guardToPromiseFn(guard, to, from)()
+ return (
+ // @ts-ignore: the guard matcheds the instance type
+ guard && guardToPromiseFn(guard, to, from, record.instances[name])()
+ )
})
} else {
const guard = rawComponent[guardType]
- guard && guards.push(guardToPromiseFn(guard, to, from))
+ guard &&
+ // @ts-ignore: the guard matcheds the instance type
+ guards.push(guardToPromiseFn(guard, to, from, record.instances[name]))
}
}
}
"@vue/compiler-core" "3.0.0-alpha.9"
"@vue/shared" "3.0.0-alpha.9"
-"@vue/compiler-sfc@3.0.0-alpha.9":
+"@vue/compiler-sfc@latest":
version "3.0.0-alpha.9"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.0.0-alpha.9.tgz#5d28d9d18fd0c4fb7fbe0e08f85f333fd7ecc8b1"
integrity sha512-Wr4O0J/lO4Q5Li6RfhZFZNIuYlBkmhk6UxxgCWdW1iPko3/C/oI9/k2SBSiRQcGCE+J5N3l/x1elYlq77YHvHA==
merge-source-map "^1.1.0"
source-map "^0.6.1"
-vue@^3.0.0-alpha.9:
+vue@next:
version "3.0.0-alpha.9"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.0.0-alpha.9.tgz#f84b6b52caf6753a8cefda370bd6bbd298b5b06a"
integrity sha512-zZrfbchyCQXF/+9B5fD4djlqzZ2XB39MxDzTaJKfuMjs/CgD2CTDiEVrOcP9HwMjr48cpARiFH13vvl4/F73RA==