typescript 如何实现一个可以动态扩充的类型声明?

类似vue-router 类型化路由,但是不通过外部插件,纯ts手段。

比如两个函数:

export function definePath<T extends string>(path: string)
export function getPath(): string

我想做到:

definePath('/a')
definePath('/b')

getPath() => '/a' | '/b'

其中definePath可以在任何地方引入,无法收归到一个文件里。

能做到吗?

阅读 444
avatarAI BotBETA

在 TypeScript 中,实现一个可以动态扩充的类型声明并且达到你所描述的效果(即 getPath() 函数返回一个包含所有通过 definePath 添加的路径的联合类型),并不直接支持,因为 TypeScript 的类型系统本质上是静态的,在编译时确定类型。而你所希望实现的是运行时的动态行为影响编译时的类型,这在 TypeScript 中是一个挑战。

然而,可以通过一些技巧接近这个效果,但请注意,这些方法都有其局限性,并且通常涉及到全局变量或类型声明文件的修改,这在大型项目中可能不是最佳实践。

下面是一个可能的解决方案,使用全局类型变量和 TypeScript 的声明合并特性:

  1. 创建一个全局类型变量来收集路径

    首先,我们需要在全局作用域中定义一个类型,用于存储所有定义的路径。这可以通过在全局声明文件中(例如 global.d.ts)定义一个接口来实现。

    // global.d.ts
    declare global {
      interface PathRegistry {
        paths: string[];
      }
      const _PathRegistry: PathRegistry;
    }
    
    export {}; // 确保这是一个模块文件

然后,在你的代码中,你需要确保这个全局变量被初始化(如果尚未初始化)。

// 在你的某个初始化代码中(例如 main.ts 或 app.ts)
if (!_PathRegistry) {
  _PathRegistry = { paths: [] };
}
  1. 定义 definePathgetPath 函数

    接下来,我们定义 definePathgetPath 函数,并更新全局的路径注册表。

    // path-manager.ts
    import '_PathRegistry'; // 确保全局变量可用
    
    type KnownPaths = _PathRegistry['paths'][number];
    
    export function definePath<T extends string>(path: T) {
      if (!_PathRegistry.paths.includes(path)) {
        _PathRegistry.paths.push(path);
      }
    }
    
    export function getPath<T extends KnownPaths>(path: T): T;
    export function getPath(): KnownPaths;
    export function getPath(path?: string): string {
      if (path && _PathRegistry.paths.includes(path as string)) {
        return path as KnownPaths;
      }
      // 注意:这里简单返回第一个路径作为示例,实际逻辑可能需要根据需求调整
      return _PathRegistry.paths[0] as KnownPaths;
    }
  2. 使用类型守卫或类型断言

    由于 TypeScript 无法在编译时自动推断 getPath() 的返回类型为所有定义路径的联合类型,你可能需要在使用 getPath() 时使用类型断言或类型守卫来确保类型安全。

    definePath('/a');
    definePath('/b');
    
    const path = getPath(); // 类型为 string,但你知道它应该是 '/a' | '/b'
    if (path === '/a' || path === '/b') {
      // 这里的 path 被缩小为 '/a' | '/b'
    }

注意

  • 上述方法依赖于全局变量 _PathRegistry,这在实际应用中可能不是最佳实践,特别是在大型项目中,因为它可能导致难以追踪的副作用和依赖。
  • TypeScript 的类型系统本质上是静态的,而上述方法试图通过运行时操作来影响编译时类型,这可能导致类型安全和一致性问题。
  • 更推荐的做法是使用枚举、常量或配置文件来管理这些路径,并在需要时使用它们进行类型检查和验证。

如果你的应用场景允许,考虑使用更静态的方法或工具来管理你的路径和类型。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
logo
Microsoft
子站问答
访问
宣传栏