Comments (32)
I'm with @aztalbot I think.
We can provide functions to provide the router as well as inject it in components where needed:
import { provideRouter, useRouter } from 'vue-router'
import router from './router'
new Vue({
setup() {
provideRouter(router)
}
})
// ... in a child component
export default {
setup() {
const { route /*, router */ } = useRouter()
const isActive = computed(() => route.name === 'myAwesomeRoute')
return {
isActive
}
}
}
- providing the router to the app is a bit more verbose, but also a bit less magical and more explicit?
- We can use the route in
setup
without having to forcing it through the prototype as a$route
property on each component - We can even choose to keep it in
setup
only and not expose it if we only are interested in derrived values (see example above) - we can rename the route object easily before exposing is to the template if we want to etc. (usual "hooks" API advantages)
- No more global namespace "pollution" like
$route
and$router
necessary
from rfcs.
Currently in vue-function-api
we can get $router
and $route
from context.root
:
setup(props, context) {
const path = computed(() => context.root.$route.path)
...
}
And I agree it would be better to have something like useRoute
.
from rfcs.
@phiter Well, Vue 3 will have a plugin API as well. Following the proposal #29, mounting an app will work a wee bit differently, but we still have a .use()
method and we still have, i.e. a global .mixin()
method.
So let's compare before/after:
A plugin like SweetAlert would usually have an install function like this in vue 2:
export default function install ( Vue, options) {
const swal = doSpoemthingWithOptions(options)
Vue.prototype.$swal = swal
}
This is nice and short but has the not so optimal consequence that the prototype gets littered with properties that people try to namespace with $name
conventions.
In Vue 3, this would work something like this, better ideas notwithstanding:
import { provide, inject } from 'vue'
const key = new Symbol('swal')
export default function install (app, options) {
const swal = doSomethingWithOptions(options)
// using a global mixins here to add a global setup().
// maybe we can have an `app.setup()` shortcut?
// #29 was written long before we came up with that new API
app.mixin({
setup() {
provide(key, swal)
}
})
}
export function useSwal() {
return inject(key)
}
Usage:
// setup
import { createApp } from 'vue'
import App from './App.vue'
import VueSweetAlert from 'vue-sweet-alert'
const app = createApp(App)
app.use(VueSweetAlert, { /* some options */})
app.mount('#app')
// in component:
import { useSwal } from 'vue-sweet-alert'
export default {
setup() {
return {
swal: useSwal()
}
}
}
Now, this may seem a bit more verbose, and it is, but it also is more explicit as well as easier to type in TS, it uses Vue's dependency injection (provide/inject
) and therefore leaves the prototype of Vue clean and untouched.
Now you still might feel that you do want to inject this into every component because you use it so regularly.
In that case, you could do this instead:
app.mixin({
setup() {
return {
$swal: useSwal()
}
}
})
and last but not least don't forget that extending the prototype is still possible. you just won't be able to access those properties from within setup()
as things currently stand.
from rfcs.
My current approach (to avoid disrupting the codebase too much) is to have the standard "2.x" router setup and then use this:
export function useRouter(context: Context) {
const router = (<any>context.root).$router as VueRouter;
const copyRoute = (r: Route) => ({
name: r.name,
path: r.path,
fullPath: r.fullPath,
params: cloneDeep(r.params),
});
const route = state(copyRoute(router.currentRoute));
watch(
() => {
return (<any>context.root).$route;
},
r => {
Object.assign(route, copyRoute(r));
},
);
return {
router,
route,
};
}
I use it in my setup
s like so:
setup(props, context) {
const { router, route } = useRouter(context);
const isHome = computed(() => route.name === 'home');
return { isHome };
}
The cloneDeep
and watch thing is so that I can compute stuff off the route which it wasn't working for me by just returning the $route
.
from rfcs.
I could imagine something like this:
// App.vue
export default {
setup() {
const { route, router } = initRouter({ routes, ...otherOptions });
},
}
// MyComponent.vue
export default {
setup() {
const { route, router } = inject(ROUTER_SERVICE);
// or
const { route, router } = useRouter();
},
}
// router.ts
export const ROUTER_SERVICE: Key<RouterService> = Symbol();
const DEFAULT_OPTIONS = {
serviceKey: ROUTER_SERVICE,
}
export function initRouter(options) {
options = mergeOptions(options, DEFAULT_OPTIONS);
const router = new Router(options);
const route = router.currentRoute;
const service = { router, route };
provide(options.serviceKey, service);
return service;
}
export function useRouter(serviceKey = ROUTER_SERVICE) {
return inject(serviceKey);
}
from rfcs.
I don't know if there will be a way to inject things to context but the router
can be directly imported and it gives access to currentRoute
(which is currently not public API but could be eventually exposed as public)
from rfcs.
Hello,
I used the inject/provide example from the RFC, but I had a reactivity issue when I wanted to watch or compute from router.currentRoute...
So I provided a reactive router instead of the router instance as is. And it solved my problem:
import Vue from 'vue'
import VueRouter from 'vue-router'
import { provide, inject, reactive } from '@vue/composition-api'
Vue.use(VueRouter)
const router = new VueRouter({ /* ... */ })
const RouterSymbol = Symbol()
export function provideRouter() {
provide(RouterSymbol, reactive(router))
}
export function useRouter() {
const router = inject(RouterSymbol)
if (!router) {
// throw error, no store provided
}
return router as VueRouter
}
If it can be of some help for anyone...
from rfcs.
I tried, and it work fine
// composables/use-router.js
import { provide, inject } from '@vue/composition-api'
const RouterSymbol = Symbol()
export function provideRouter(router) {
provide(RouterSymbol, router)
}
export default function useRouter() {
const router = inject(RouterSymbol)
if (!router) {
// throw error, no store provided
}
return router
}
------------------
// App.vue
setup(props, { root: { $router} }) {
provideRouter($router)
------------
// Usage in component
import useRouter from './user-router'
export default () => {
const router = useRouter()
......
But don't work with $route
(Do the same this, but change context $router -> $route
)
Could help me?
from rfcs.
A simple alternative to the current api.
// hooks/use-router.ts
import { computed, getCurrentInstance } from '@vue/composition-api';
export const useRouter = () => {
const vm = getCurrentInstance();
if (!vm) {
throw new ReferenceError('Not found vue instance.');
}
const route = computed(() => vm.$route);
return { route, router: vm.$router } as const;
};
from rfcs.
@jods4 I chose computed for just one reason:
- We can use
useRouter()
in components that always remain mounted. And who needs to know the route changes.
from rfcs.
@beeplin Good question. I've been wondering whether, when using this new API, it is preferable to use everything as a hooks:
const { route, router } from useRouter()
console.log(route.params.id)
const goHome = () => {
router.push({ name: 'home' })
}
I assume Vue.use
would still be needed to install components like router-view
. But if route
and router
need to be made available on context
, I'm curious what TypeScript implications that would have.
from rfcs.
Alternatively to 1., we could have the router object expose the "hook":
import router from './router'
new Vue({
setup() {
router.use()
}
})
from rfcs.
This will "break" every existing plugin I think, since you cannot access "this.$plugin" directly anymore.
They'll all have to update themselves to use the new API and I don't know how exactly that would work after you install it with options. You can't just import them from the lib right?
from rfcs.
This will "break" every existing plugin I think, since you cannot access "this.$plugin" directly anymore.
It will only "break" insofar as as plugins that don't yet provide function for use in setup
can't be used in setup()
and instead have to be used in the current way (object API), so it's not a breaking change in the semver sense.
Since Vue 3 is a major release that comes with other breaking changes, many plugins will have to update either way to stay compatible, and for others, upgrading to work with setup
as well seems like a logical step.
Getting back from "plugins" as a whole to the router in particular, I feel that the Vue 3 release would be a good moment to update the API and get rid of the prototype properties that we don't consider ideal anymore, especially since we will have support for provide/inject
from the get-go in Vue 3 (it was intorduced post 2.0 in the current major), and migration seems to be straightforward / could be automated with codemods.
I don't know how exactly that would work after you install it with options. You can't just import them from the lib right?
I'm not sure what you are referring to here.
from rfcs.
I'm not sure what you are referring to here.
I was referring to plugins that add options to the instance, like Vue SweetAlert.
You can use this.$swal
in the component.
With the new api, those plugins would have to allow you to do import { swal } from 'vue-swal'
.
But that wouldn't load the plugin options, unless it somehow recognizes the options you pass when you init it using Vue.use(plugin)
.
from rfcs.
The cloneDeep and watch thing is so that I can compute stuff off the route which it wasn't working for me by just returning the $route.
By using a value() instead of state you don't need the copy.
export function useRouter(context: Context) {
const router = (<any>context.root).$router as VueRouter;
const route = value(router.currentRoute);
watch(
() => {
return (<any>context.root).$route;
},
r => {
route.value = r
},
);
return {
router,
route,
};
}
from rfcs.
By using a value() instead of state you don't need the copy.
I tried that @LinusBorg but I get a strange error:
Cannot assign to read only property 'meta' of object '#<Object>'
I believe it's the Route
interaction with some current internals of https://github.com/vuejs/vue-function-api
Also a user would then have to use route.value.name
instead of just route.name
.
An aside, for the solutions using provide
, the current "2.x" plugin will only consider the last provide
in the setup
so multiple provide
do not work as expected.
(perhaps all of this should go in the plugin repo instead of here)
from rfcs.
(perhaps all of this should go in the plugin repo instead of here)
probably
from rfcs.
You can't use inject in a function component if I remember correctly. You have to use an actual component.
from rfcs.
It work
for $router (.push()....)
, but don't work
with $route (.params, ....)
from rfcs.
That's not Javascript. I don't know what you want to say.
from rfcs.
That's not Javascript. I don't know what you want to say.
Sorry my reply is sort.
It mean that:
- Work fine with
$router
injection
$router.push(....)
- Don't work with
$route
injection (I tried new injection withthis.$route
)
$route.params
from rfcs.
My complete code to create injection of this.$route
(NOT this.$router
)
// composables/use-route.js
import { provide, inject } from '@vue/composition-api'
const RouteSymbol = Symbol()
export function provideRoute(route) {
provide(RouteSymbol, route)
}
export default function useRoute() {
const route = inject(RouteSymbol)
if (!route) {
// throw error, no store provided
}
return route
}
------------------
// App.vue
export default () => {
setup(props, { root: { $route} }) {
provideRoute($route)
------------
// Usage in component
import useRoute from './user-route'
export default () => {
setup(){
const route = useRoute()
console.log(route)
......
}
--------------- Result -----------
name: null
meta: {}
path: "/"
hash: ""
query: {}
params: {}
fullPath: "/"
matched: []
Don't work, my route
{
path: '/login',
name: 'login',
component: () => import('../../ui/pages/Login.vue'),
meta: {
layout: 'Public',
},
},
from rfcs.
@LinusBorg are your examples from #70 (comment) still valid?
as i understand we clearly dont want to execute this plugin setup function with every component:
app.mixin({
setup() {
provide(key, swal)
}
})
is there a way to only hook into the setup of the App component?
from rfcs.
@backbone87 If you mean implicitly, I don't know how. For Vue 2.x plugins, I usually check if this === this.$root
or something similar inside global mixins.
Now that plugins apply at app-level, not globally, it would be nice if we could hook into app "lifecycle hooks", e.g. app.onMounted
, app.onUnmounted
, inside plugins.
from rfcs.
@negezor you don't even need the computed. Just go for a getter:
return {
get route() { return vm.$route },
router: vm.$router
}
Bonus chatter: there are differences between these 2 approaches:
- computed caches its value (useful if the computation is costly);
- computed is reactive itself and will be watched instead of its source (useful if there are many consumers watching the same computed);
- the result of an accessor will be proxified automatically, which won't be the case for the computed (can be a pitfall).
from rfcs.
@negezor what's the difference with the getter?
You can use it in components that remain mounted and they will know when it changes just the same.
from rfcs.
@jods4 In two cases:
- Destruction at the beginning of setup()
- Use in the template without the $ prefix
setup() {
const { route } = useRouter();
return { route }
}
from rfcs.
Extracting the value is not reactive.
I meant, what's the difference with this getter:
return {
get route() { return vm.$route },
router: vm.$router
}
from rfcs.
@jods4 the property itself is not reactive, but after watchEffect should the value be subtracted again?
UDP: I made a test sandbox for an example
from rfcs.
Has there been any update on this over the last few months? Several of the options in this thread no longer appear to work and I am wondering why there has been no update. I haven't even been able to get access to setup
's context
argument (just an empty object). The project was created using the Vue CLI and the 3 template.
UPDATE: My apologies, I was directed to the vue-router-next
playground, where I found the useRouter
hook. Is there any known documentation for this that I missed, or is it currently semi-hidden?
@negezor This sandbox example no longer works as of Aug 15.
from rfcs.
I used to use ctx.root.$route
in setup function prior to vue-2.7
. now with vue-2.7
ctx.root
is undefined. I wonder how do you people got to solve the issue of accessing the route and it's params in a reactive way?!
from rfcs.
Related Issues (20)
- Adding a .v shortcut for .value HOT 4
- [FEAT] Required slots HOT 1
- Add abstraction for System Modifier Key: ctrl+c on Windows equals cmd+c on macOS HOT 1
- Pure TypeScript props declaration for the Options API, like we have defineProps() for the Composition API? HOT 4
- <script setup> : Make it possible to call defineProps many times HOT 10
- All Transition and TransitionGroup JavaScript hooks should be async
- Make runtime Props validation optional HOT 5
- Teleport as a composable/function
- An option to generate scoped styles using `:where` HOT 6
- Provide/inject for EffectScope
- Compiler: whitelist `import` global in template HOT 2
- Native CSS Modules
- Add impassive event modifier !passive HOT 2
- Stop script setup from executing any further HOT 1
- AsyncFunctionalComponent HOT 4
- Add a '.noinherit' modifier HOT 2
- Feature Request: `$emit` to return a Promise notifying when the event handler has run HOT 5
- Vue SSR app renderToString catch errors/warnings
- [Warnings] Silence Vue warnings in DEV
- expose 支持传入响应式对象 HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from rfcs.