Open bponomarenko opened 4 years ago
Alternatively, access to only componentOptions
should be enough if componentOptions
will have access to the parent component (like vm.$options
). In this case it would be possible to get access to:
componentOptions.parent.$router
componentOptions.parent.$store
@posva I understand that you are actively working on the next version of the router, and this kind of feature can significantly improve usage of the vue-router
in more complex scenarios like ours.
From what I see in the vue-router-next
code, this feature can be added to the navigationGuard.ts
:
export function guardToPromiseFn(
guard: NavigationGuard,
to: RouteLocationNormalized,
from: RouteLocationNormalizedLoaded,
instance?: ComponentPublicInstance | undefined,
resolvedComponent?: RouteComponent | undefined,
): () => Promise<void> {
...
// wrapping with Promise.resolve allows it to work with both async and sync guards
Promise.resolve(guard.call(instance, to, from, next, resolvedComponent)).catch(err =>
reject(err)
)
}
export function extractComponentsGuards(
matched: RouteRecordNormalized[],
guardType: GuardType,
to: RouteLocationNormalized,
from: RouteLocationNormalizedLoaded
) {
...
if (typeof rawComponent === 'function') {
...
return (
guard && guardToPromiseFn(guard, to, from, record.instances[name], resolvedComponent)()
)
} else {
const guard = rawComponent[guardType]
guard &&
// @ts-ignore: the guards matched the instance type
guards.push(guardToPromiseFn(guard, to, from, record.instances[name], rawComponent))
}
}
What do you think? If that works, it looks like it can easily be backported to vue-router v3
This is pretty much a duplicate of https://github.com/vuejs/vue-router/issues/2118
Importing the router
instance (or the store) do not work on SSR anyway, so it's not a solution (I've stated the opposite in older issues because I wasn't considering SSR in some issues).
Note accessing the app
through the router instance is a hack. When dealing with SSR, a app with a new router and new store is created for each request (eg https://github.com/vuejs/vue-hackernews-2.0/blob/master/src/entry-client.js) allowing referencing each router and store inside of that context in beforeEach
guards. In Nuxt, there is a Context concept that allows accessing a request context on server to not share information between requests.
It's important for me to understand what is this solving. Is it accessing to the store
associated to the running application? What about a router being used by multiple applications, they could each having different stores and there are situations where the navigation could come from outside of an application. I could make the router instance app-aware while being accessed through this.$router
(and userRouter
on v4) but if someone imports the router (valid in SPA contexts) or rely on a global variable (micro frontends maybe), there is no way to detect that and it would be very confusing to debug.
Most of the time, it is possible to replace beforeRouterEnter
with global router.beforeResolve
, in that scenario you have access to the router
and store
instances. If it's not possible to consistently provide these in beforeRouteEnter
, it's fair to consider a design limitation as it can be solved in a different way.
I don't think it makes sense to provide the router instance as this
in beforeEach
because of https://github.com/vuejs/vue-router/issues/2118#issuecomment-375236584. beforeRouteEnter
is a different story, since it's inside a component and it could need access to the store
as well as other global properties
Hi @posva. Thanks for coming back on this one.
This is pretty much a duplicate of #2118. Importing the
router
instance (or the store) do not work on SSR anyway, so it's not a solution...
I do agree with you on this one. That's why I created different ticket with alternative API proposal, which suppose to solve the same issue. Also because this proposal is limited to beforeRouteEnter
guard.
Note accessing the
app
through the router instance is a hack.
That is complete surprise for me, as app
reference is part of the official API docs for the router. Also I wasn't aware about use cases when single router instance would be used for multiple apps, so originally proposed solution doesn't take that into account.
It's important for me to understand what is this solving.
My initial intention was to implement "data fetching before navigation" concept but with the use of Vuex store. To achieve that I need to have access to the store
instance inside beforeRouteEnter
hook. Since access to this
is not available (because component instance is not created yet at this moment of navigation lifecycle), and I also cannot import store
reference (because components are re-used between different apps with different root store
instance), I was looking for an alternative ways to obtain this reference.
Most of the time, it is possible to replace
beforeRouterEnter
with globalrouter.beforeResolve
...
That is an interesting idea and I would investigate it, however I can already see one challenge with it. beforeRouterEnter
and beforeRouteUpdate
are defined in the component, encapsulating all logic for data load next to the template and the rest of component implementation. Also they clearly separate situations when component is rendered for the first time and when it is re-used between routes. Delegating data load to the router.beforeResolve
breaks this encapsulation and makes it tricky (if not impossible) to distinguish newly rendered and re-used components.
Looking forward for your thoughts on this.
That is complete surprise for me, as app reference is part of the official API docs for the router.
That's true. Then it's not a hack... It doesn't work on multiple apps but at the same time you don't do SSR when having multiple apps, so I guess it's fine. It won't be the same on v4 (where it currently doesn't exist) because it could only point to the app returned by createApp
. But that's a different topic anyway as it concerns Vue 3.
My guess is that data fetching might not belong in the routing but even with global beforeResolve
, you can achieve code splitting because you can directly access the component option through the matched
array in the to
parameter.
My guess is that data fetching might not belong in the routing but even with global
beforeResolve
, you can achieve code splitting because you can directly access the component option through thematched
array in theto
parameter.
That is true, and that is something I want to investigate in our app. However, as I mentioned, there is no way to tell which of those matched
components are newly-rendered and which are re-used from the previous route. Unless maybe by comparing matched
arrays from to
and from
routes.
Unless maybe by comparing matched arrays from to and from routes.
Yes. They are also ordered from parent to child. You can checkout the source code to see how we do it
After some experiments, it looks like beforeRouterEnter
cannot be easily replaced with router.beforeResolve
.
Yes, router.beforeResolve
now has access to the router
and store
instances, but it is missing one crucial part – possibility to pass a callback to the next
function, which will be executed with the reference to the created component instance. As a result this solution is not really usable when there is need to have access to both component and store
instances. I would expect that custom implementation of the functionality similar to next(vm => { ... })
would be cumbersome and error prone, since neither router.beforeResolve
nor router.afterEach
has access to the component instance.
+1, is really needed for components shared with lerna (can't import store instance). Got to same exact issue on our end, what's going on with this?
In my case there are several apps with their router instances, and the component is unaware which one in use, so it cannot import router
or app
directly. And using global beforeEach
or beforeResolve
instead of component-level hook breaks modularity. I wonder what was the problem with providing this
context to beforeRouteEnter
, this wouldn't be a breaking change. From what I see, there's no way to augment this functionality from the outside without replicating the whole component router hook system.
I have just exported the root component and import it as I need into my components:
// main.ts
export default createApp(App).use(router).mount("#app");
<script lang="ts">
import { useRouter } from "vue-router";
import main from "../main";
async function load() {
const router = useRouter();
await new Promise((r) => setTimeout(r, 1000));
await router.replace({ name: "index" });
}
</script>
<script setup lang="ts">
defineOptions({
async beforeRouteEnter() {
await main.$root.$.appContext.app.runWithContext(load);
},
});
</script>
Extremely not pretty, but seems working.
EDIT: useRouter
is used as an example, my use case is accessing a global api provide
d by another plugin.
I think that you might find this Data Loaders RFC interesting: https://github.com/vuejs/rfcs/discussions/460#discussioncomment-6172711 It takes away the boilerplate you seem to have and no need to rely on the root component which can be easily abused
I do find it very interesting, in fact I am trying to implement something inspired by that RFC.
Maybe I should have a looksie at how it is implemented in unplugin-vue-router
.
Are global injects accessible in unplugin-vue-router
s defineLoader
?
I think they were. At least, I do plan on making them available. Be aware that the loaders are subject to change.
What problem does this feature solve?
There are some use cases, when it is required to have access to the router instance and component options inside
beforeRouteEnter
guard. WhilebeforeRouteUpdate
andbeforeRouteLeave
could get such access throughthis.$router
andthis.$options
, it seems not way to achieve it inbeforeRouteEnter
. Importing router instance (similar to store import suggestion here) is not an option for us because we have shared set of router components, which are re-used in different vue applications, so there is no single place to import vuex store from.Use case
I'm trying to create generic plugin for our applications which will define hook to pre-load data. In some component data pre-load will happen via vuex store, so it should be accessible in this hook.
Here is simplified code for the plugin:
This plugin will make it possible to define custom
preloadData() { ... }
component option for a generic data pre-load as part of routing process. The only missing references to make it work arecomponentOptions
androuter
.Note that it is possible to provide
router
access in this particular example by passing it as plugin argument –export default function install(Vue, router) { ... }
. However, it might be still beneficial to have access to router in thebeforeRouteEnter
guard, as it will be a solution for #3157.What does the proposed API look like?
Proposal is to add additional
guardContext
argument to the guard function:However if it should be implemented in different way – would be great to hear it.