skip to Main Content

It is easy to add add function to prototype of a value, for example:

Array.prototype.removeAt = function (index) {
  if (index >= 0 && index < this.length) {
    this.splice(index, 1);
  }
  return this;
};

declare global {
  export interface Array<T> {
    removeAt(index: number): Array<T>;
  }
}

But it does not work any more when I try to add function to prototype of an interface (which is actually a class, so it has prototype). For example:

import { Router, RouteRecord } from 'vue-router';

Router.prototype.getRouteByName = function (this: Router, name: string): RouteRecord | undefined {
  return this.getRoutes().find(m => m.name === name);
};

declare global {
  export interface Router {
    getRouteByName(name: string): RouteRecord | undefined;
  }
}

I get an error when I compile it: 'Router' only refers to a type, but is being used as a value here.

However, the type ‘Router’ is exported as an interface while it is actually a class in the JavaScript code behind, so I think there must be a way to bypass this restriction and mutate its prototype.

Here is how Router is exported:

export interface Router {
 // so many members, please see the detains on 
 // https://github.com/vuejs/router/blob/main/packages/router/src/router.ts#L189
}

What should I do to achieve it?

2

Answers


  1. Router is an interface here, not a class function, and more over a router instance is just a plain object so you cannot extend any EXPOSED prototype here, your code doesn’t even run:

      const router = { // returned from createRouter
        currentRoute,
        listening: true,
        addRoute,
        removeRoute,
        clearRoutes: matcher.clearRoutes,
        hasRoute,
        getRoutes,
        resolve,
        options,
        push,
        replace,
        go,
    ...
    

    Just override createRouter() any way you like, here I use a class:

    import { createRouter as createVueRouter, createWebHistory } from 'vue-router';
    
    interface MyRouter extends ReturnType<typeof createVueRouter> { };
    class MyRouter implements MyRouter {
        getRouteByName(name: string) {
            return this.getRoutes().find(m => m.name === name);
        };
    }
    
    export function createRouter(...params: Parameters<typeof createVueRouter>): MyRouter {
        const router = createVueRouter(...params);
        Object.setPrototypeOf(router, MyRouter.prototype);
        return router as MyRouter;
    }
    
    const router = createRouter({
        history: createWebHistory(), routes: [{
            path: '/',
            name: 'test',
            component: () => import('./test.vue')
        }]
    });
    
    const route = router.getRouteByName('test');
    

    So import this function and use it instead of the original createRouter

    Login or Signup to reply.
  2. You extend this not through declare global but

    import type { Router, RouteRecord } from 'vue-router';
    // I'm not sure which one you need from these two.
    import 'vue-router';
    
    declare module 'vue-router' {
       // not sure if you need the export keyword here
       export interface Router {
           getRouteByName(name: string): RouteRecord | 
               undefined;
       }
    }
    

    You either need to import this file wherever you wanna use getRouteByName or you have to add it as an ambient type declaration file to you tsconfig. (typeRoots, files,…not sure)

    Btw, router is actually a plain JS object so prototyping it might not be so productive just create your own router plugin or router factory function like createRouter that wraps createRouter.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search