TypeScript中如何实现类型安全的路由系统

 更新时间:2025年10月25日 14:51:14   作者:代码保安  
本文讲述TypeScript如何实现类型安全的路由系统,路由类型结构、使用infer和Extract提取参数类型、定义路由配置类型、创建路由表、实现类型安全的导航和位置钩子等,使用React Router v6或Next.js App Router,结合zod进行查询参数验证,以实现类型安全的路由系统

在 TypeScript 中实现一个类型安全的路由系统,可以通过 类型推导类型约束 来确保路由路径、参数、查询参数和组件类型之间的强一致性。以下是实现一个类型安全路由系统的完整方案:

目标

  • 路由路径类型安全(路径错误编译时报错)
  • 路由参数类型安全(路径参数类型正确)
  • 查询参数类型安全(查询参数类型正确)
  • 路由组件类型安全(路由组件类型正确)
  • 支持嵌套路由

1. 定义路由类型结构

我们使用一个 RouteConfig 类型来定义路由结构,支持嵌套和参数。

type RouteConfig<T extends string> = {
  path: T;
  component: React.ComponentType<any>;
  children?: RouteConfig<any>[];
  params?: Record<string, string>; // 可选参数类型
  query?: Record<string, string>; // 查询参数类型
};

但更推荐使用 泛型 + 映射类型 来实现类型安全。

2. 使用infer和Extract实现路径参数类型推导

我们使用 infer 来从路径字符串中提取参数类型。

// 从路径字符串中提取参数类型
type PathParams<T extends string> = T extends `${infer P}/${infer R}`
  ? P extends `:${infer Param}`
    ? { [K in Param]: string } & PathParams<R>
    : PathParams<R>
  : {};

示例:

type P1 = PathParams<'users/:id'>; // { id: string }
type P2 = PathParams<'users/:id/posts/:postId'>; // { id: string; postId: string }

3. 定义路由配置类型(支持嵌套)

type Route<T extends string> = {
  path: T;
  component: React.ComponentType<any>;
  children?: Route<T>[];
  params?: PathParams<T>; // 参数类型
  query?: Record<string, string>; // 查询参数类型
};

4. 创建路由表(类型安全)

const routes = {
  home: {
    path: '/',
    component: Home,
  },
  users: {
    path: '/users',
    component: Users,
    children: [
      {
        path: '/users/:id',
        component: UserDetail,
        params: { id: 'string' },
      },
      {
        path: '/users/:id/posts/:postId',
        component: PostDetail,
        params: { id: 'string', postId: 'string' },
      },
    ],
  },
} as const;

使用 as const 强制类型推导为字面量类型,防止类型丢失。

5. 实现类型安全的useNavigate和useLocation

useNavigate类型安全

type NavigateOptions<T extends string> = {
  to: T;
  query?: Record<string, string>;
};

function useNavigate() {
  const navigate = useNavigate(); // 假设使用 React Router v6

  return <T extends string>(options: NavigateOptions<T>) => {
    const { to, query } = options;
    const search = query ? new URLSearchParams(query).toString() : '';
    navigate(`${to}${search ? `?${search}` : ''}`);
  };
}

useLocation类型安全

function useLocation<T extends string>() {
  const location = useLocation();
  const path = location.pathname as T;

  // 提取路径参数
  const params = path.split('/').reduce((acc, segment, index, arr) => {
    if (segment.startsWith(':')) {
      const key = segment.slice(1);
      const value = arr[index + 1];
      return { ...acc, [key]: value };
    }
    return acc;
  }, {} as Record<string, string>);

  // 查询参数
  const searchParams = new URLSearchParams(location.search);
  const query = {} as Record<string, string>;
  searchParams.forEach((value, key) => {
    query[key] = value;
  });

  return { path, params, query };
}

6. 使用useNavigate和useLocation的示例

function UserDetail() {
  const { params, query } = useLocation<'/users/:id'>();

  const navigate = useNavigate();

  return (
    <div>
      User: {params.id}
      <button onClick={() => navigate({ to: '/users', query: { page: '2' } })}>
        Go to Users
      </button>
    </div>
  );
}

7. 类型安全的Link组件

type LinkProps<T extends string> = {
  to: T;
  query?: Record<string, string>;
  children: React.ReactNode;
};

function Link<T extends string>({ to, query, children }: LinkProps<T>) {
  const navigate = useNavigate();

  return (
    <button onClick={() => navigate({ to, query })}>
      {children}
    </button>
  );
}

8. 使用React Router的类型安全封装

如果你使用 react-router-dom,可以使用 useParamsuseSearchParams 并结合类型推导。

function UserDetail() {
  const { id } = useParams<{ id: string }>();
  const [searchParams] = useSearchParams();

  const postId = searchParams.get('postId');

  return <div>User {id} Post {postId}</div>;
}

useParamsuseSearchParams 本身支持类型推导,但需要手动指定类型。

9. 高级:使用zod+TypeScript实现查询参数验证

import { z } from 'zod';

const UserQuerySchema = z.object({
  page: z.string().optional(),
  limit: z.string().optional(),
});

type UserQuery = z.infer<typeof UserQuerySchema>;

然后在 useLocation 中使用:

const query = UserQuerySchema.parse(Object.fromEntries(searchParams));

10. 总结:类型安全路由系统的关键点

功能实现方式
路径类型安全使用 infer 提取路径参数
参数类型安全params 字段显式定义类型
查询参数类型安全query 字段定义类型,配合 zod 验证
组件类型安全component 类型为 React.ComponentType<any>
路由跳转安全useNavigate 类型约束 to 路径
嵌套路由使用 children 字段递归定义

最终建议

  • 使用 React Router v6Next.js App Router(推荐)
  • 使用 zod 验证查询参数
  • 使用 as const 固定路由配置类型
  • 使用 infer 提取路径参数类型
  • 使用 useNavigateuseLocation 封装类型安全接口

示例项目结构

src/
  routes/
    index.ts
    user.ts
  components/
    Home.tsx
    UserDetail.tsx
  hooks/
    useNavigate.ts
    useLocation.ts

通过以上方式,你可以构建一个 完全类型安全 的路由系统,避免运行时错误,提升开发体验。

到此这篇关于TypeScript中如何实现类型安全的路由系统的文章就介绍到这了,更多相关TypeScript实现路由系统内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Js 本页面传值实现代码

    Js 本页面传值实现代码

    记得以前在学校的时候,例如要修改信息,需要要修改的部分的值显示出来,都是先把数据传到后台,然后再在前台显示的,想想真够笨的,这个可以在客户端就实现的,何必要传到后台呢
    2009-05-05
  • javascript eval和JSON之间的联系

    javascript eval和JSON之间的联系

    本文着重解释eval函数和JSON数据格式之间的联系以及一些细节上的问题。
    2009-12-12
  • JS库之Highlight.js的用法详解

    JS库之Highlight.js的用法详解

    highlight.js是一款轻量级的Web代码语法高亮库。下面通过实例代码给大家分享JS库之Highlight.js的用法详解,感兴趣的朋友跟随脚本之家小编一起学习吧
    2017-09-09
  • JS关于刷新页面的相关总结

    JS关于刷新页面的相关总结

    在本篇内容中我们给大家整理了关于JS刷新页面的所有相关知识点以及整理了相关的技术文章,大家可以收藏本页面继续深入学习。
    2018-05-05
  • jquery div模态窗口的简单实例

    jquery div模态窗口的简单实例

    下面小编就为大家带来一篇jquery div模态窗口的简单实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-05-05
  • 用JS实现HTML标签替换效果

    用JS实现HTML标签替换效果

    用JS实现HTML标签替换效果...
    2007-06-06
  • 微信小程序单选框自定义赋值

    微信小程序单选框自定义赋值

    这篇文章主要介绍了微信小程序单选框如何自定义赋值,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-05-05
  • HTML DOM Viewer

    HTML DOM Viewer

    HTML DOM Viewer...
    2006-09-09
  • JavaScript简单实现鼠标移动切换图片的方法

    JavaScript简单实现鼠标移动切换图片的方法

    这篇文章主要介绍了JavaScript简单实现鼠标移动切换图片的方法,涉及JavaScript针对鼠标事件的响应及页面元素的动态变换技巧,需要的朋友可以参考下
    2016-02-02
  • javascript css styleFloat和cssFloat

    javascript css styleFloat和cssFloat

    在写js操作css的过程中发现float属性在IE和firefox下对应的js脚本是不一样的,IE下对应得是 styleFloat,firefox,chorme,safari下对应的是cssFloat,可用in运算符去检测style是否包含此属性。
    2010-03-03

最新评论