网站搜索

javascript - 如何向实际上是类的接口的原型添加函数?


将函数添加到一个值的原型上很容易,例如:

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>;
  }
}

但是当我试图向接口的原型添加函数时(它实际上是一个类,所以它有原型),它不再起作用。例如:

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;
  }
}

当我编译它时出现错误:'Router' 仅指类型,但这里被用作值。

然而,类型 'Router' 被导出为一个接口 而实际上它在后端的 JavaScript 代码中是一个类,所以我认为一定有办法绕过这个限制并修改它的原型。

以下是 Router 的导出方式:

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

我应该做什么来实现它?

答案#1

Router 在这里是一个接口,不是类函数,而且更重要的是,路由实例只是一个普通对象,所以你不能在这里扩展任何暴露的原型,你的代码甚至无法运行:

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

只需以任何你喜欢的方式重写 createRouter(),这里我使用一个类:

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');

所以导入这个函数并使用它来替代原来的 createRouter

答案#2

你不是通过 declare global 扩展这个,而是

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;
   }
}

你要么需要在想要使用 getRouteByName 的地方导入这个文件,要么必须将其添加为你的 tsconfig 的全局类型声明文件。(typeRoots, files,...不确定)

顺便说一下,路由器实际上是一个普通的 JS 对象,所以给它做原型扩展可能并不太有用,只需创建你自己的路由器插件或路由器工厂函数,比如包装 createRouter 的 createRouter。