Giter VIP home page Giter VIP logo

Comments (32)

LinusBorg avatar LinusBorg commented on August 17, 2024 12

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
    }
  }
}
  1. providing the router to the app is a bit more verbose, but also a bit less magical and more explicit?
  2. We can use the route in setup without having to forcing it through the prototype as a $route property on each component
  3. 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)
  4. we can rename the route object easily before exposing is to the template if we want to etc. (usual "hooks" API advantages)
  5. No more global namespace "pollution" like $route and $router necessary

from rfcs.

beeplin avatar beeplin commented on August 17, 2024 9

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.

LinusBorg avatar LinusBorg commented on August 17, 2024 8

@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.

thenikso avatar thenikso commented on August 17, 2024 4

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 setups 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.

backbone87 avatar backbone87 commented on August 17, 2024 3

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.

posva avatar posva commented on August 17, 2024 1

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.

plmercereau avatar plmercereau commented on August 17, 2024 1

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.

thearabbit avatar thearabbit commented on August 17, 2024 1

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.

negezor avatar negezor commented on August 17, 2024 1

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.

negezor avatar negezor commented on August 17, 2024 1

@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.

aztalbot avatar aztalbot commented on August 17, 2024

@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.

LinusBorg avatar LinusBorg commented on August 17, 2024

Alternatively to 1., we could have the router object expose the "hook":

import router from './router'

new Vue({
  setup() {
    router.use()
  }
})

from rfcs.

phiter avatar phiter commented on August 17, 2024

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.

LinusBorg avatar LinusBorg commented on August 17, 2024

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.

phiter avatar phiter commented on August 17, 2024

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.

LinusBorg avatar LinusBorg commented on August 17, 2024

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.

thenikso avatar thenikso commented on August 17, 2024

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.

LinusBorg avatar LinusBorg commented on August 17, 2024

(perhaps all of this should go in the plugin repo instead of here)

probably

from rfcs.

LinusBorg avatar LinusBorg commented on August 17, 2024

You can't use inject in a function component if I remember correctly. You have to use an actual component.

from rfcs.

thearabbit avatar thearabbit commented on August 17, 2024

It work for $router (.push()....), but don't work with $route (.params, ....)

from rfcs.

LinusBorg avatar LinusBorg commented on August 17, 2024

That's not Javascript. I don't know what you want to say.

from rfcs.

thearabbit avatar thearabbit commented on August 17, 2024

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 with this.$route)
$route.params

from rfcs.

thearabbit avatar thearabbit commented on August 17, 2024

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.

backbone87 avatar backbone87 commented on August 17, 2024

@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.

leopiccionia avatar leopiccionia commented on August 17, 2024

@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.

jods4 avatar jods4 commented on August 17, 2024

@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:

  1. computed caches its value (useful if the computation is costly);
  2. computed is reactive itself and will be watched instead of its source (useful if there are many consumers watching the same computed);
  3. the result of an accessor will be proxified automatically, which won't be the case for the computed (can be a pitfall).

from rfcs.

jods4 avatar jods4 commented on August 17, 2024

@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.

negezor avatar negezor commented on August 17, 2024

@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.

jods4 avatar jods4 commented on August 17, 2024

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.

negezor avatar negezor commented on August 17, 2024

@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.

kendallroth avatar kendallroth commented on August 17, 2024

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.

mhDuke avatar mhDuke commented on August 17, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.