React router 6+:如何在路由加载器中强键入 params 选项?

qva*_*azi 7 typescript reactjs react-router-dom react-typescript

有路由器

export const router = createBrowserRouter([
  {
    path: '/todos/:todoId',
    element: <Todo />,
    loader: todoLoader,
  }
]);
Run Code Online (Sandbox Code Playgroud)

有装载机

export const router = createBrowserRouter([
  {
    path: '/todos/:todoId',
    element: <Todo />,
    loader: todoLoader,
  }
]);
Run Code Online (Sandbox Code Playgroud)

如何根据路径输入参数?

等待路径中指定参数的高亮显示。

更新:感谢@DrewReese @LindaPaiste 提出了以下解决方案。

export const loader: LoaderFunction = async ({ params }) => {
  return await fetchData(params.todoId);
};
Run Code Online (Sandbox Code Playgroud)

Dre*_*ese 10

主要方法

declare type _PathParam<Path extends string> = Path extends `${infer L}/${infer R}` ? _PathParam<L> | _PathParam<R> : Path extends `:${infer Param}` ? Param extends `${infer Optional}?` ? Optional : Param : never;
/**
 * Examples:
 * "/a/b/*" -> "*"
 * ":a" -> "a"
 * "/a/:b" -> "b"
 * "/a/blahblahblah:b" -> "b"
 * "/:a/:b" -> "a" | "b" 
 * "/:a/b/:c/*" -> "a" | "c" | "*"
 */
declare type PathParam<Path extends string> = Path extends "*" ? "*" : Path extends `${infer Rest}/*` ? "*" | _PathParam<Rest> : _PathParam<Path>;
export declare type ParamParseKey<Segment extends string> = [
    PathParam<Segment>
] extends [never] ? string : PathParam<Segment>;
/**
 * The parameters that were parsed from the URL path.
 */
export declare type Params<Key extends string = string> = {
    readonly [key in Key]: string | undefined;
};
Run Code Online (Sandbox Code Playgroud)

声明路径的映射/对象(键 - 值),您可以使用 RRDParamsParamParseKey实用程序类型来提取路线路径参数(方法归功于 LindaPaiste 和 Qvazi)。

declare type _PathParam<Path extends string> = Path extends `${infer L}/${infer R}` ? _PathParam<L> | _PathParam<R> : Path extends `:${infer Param}` ? Param extends `${infer Optional}?` ? Optional : Param : never;
/**
 * Examples:
 * "/a/b/*" -> "*"
 * ":a" -> "a"
 * "/a/:b" -> "b"
 * "/a/blahblahblah:b" -> "b"
 * "/:a/:b" -> "a" | "b" 
 * "/:a/b/:c/*" -> "a" | "c" | "*"
 */
declare type PathParam<Path extends string> = Path extends "*" ? "*" : Path extends `${infer Rest}/*` ? "*" | _PathParam<Rest> : _PathParam<Path>;
export declare type ParamParseKey<Segment extends string> = [
    PathParam<Segment>
] extends [never] ? string : PathParam<Segment>;
/**
 * The parameters that were parsed from the URL path.
 */
export declare type Params<Key extends string = string> = {
    readonly [key in Key]: string | undefined;
};
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

const router = createBrowserRouter([
  {
    path: Paths.todoDetail,
    element: <Todo />,
    loader: todoLoader as LoaderFunction
  },
]);
Run Code Online (Sandbox Code Playgroud)

但是,该fetchData函数需要一个string类型,因此这里存在不兼容性,因为params.idTodo类型为string | undefined。通过在访问之前进行条件检查来修复此问题

const Paths = {
  todoDetail: "/todos/:idTodo",
} as const;

interface TodoLoaderArgs extends ActionFunctionArgs {
  params: Params<ParamParseKey<typeof Paths.todoDetail>>;
}

const todoLoader: LoaderFunction = async ({ params }: TodoLoaderArgs) => {
  return await fetchData(params.idTodo);
};
Run Code Online (Sandbox Code Playgroud)

或断言它是非空的,例如fetchData(params.idTodo!),或者您可以提供后备,例如fetchData(params.idTodo ?? "")。也许后备可以是数据获取的一些默认查询参数值。

const router = createBrowserRouter([
  {
    path: Paths.todoDetail,
    element: <Todo />,
    loader: todoLoader as LoaderFunction
  },
]);
Run Code Online (Sandbox Code Playgroud)

编辑react-router-6-how-to-strongly-type-the-params-option-in-route-loader(分叉)

次要方法

这是一个有点迂回的方法,但这似乎是正确键入的并且在运行的codesandbox中工作,但可能有点“hackish”(我的Typescript foo不太好)。要点是您需要重写加载器函数args参数,以便可以重写params属性以包含要在加载器中访问的路径参数。

要覆盖的加载器定义:

/**
 * The parameters that were parsed from the URL path.
 */
export declare type Params<Key extends string = string> = {
    readonly [key in Key]: string | undefined;
};

/**
 * @private
 * Arguments passed to route loader/action functions.  Same for now but we keep
 * this as a private implementation detail in case they diverge in the future.
 */
interface DataFunctionArgs {
    request: Request;
    params: Params;
    context?: any;
}
/**
 * Arguments passed to loader functions
 */
export interface LoaderFunctionArgs extends DataFunctionArgs {
}
/**
 * Route loader function signature
 */
export interface LoaderFunction {
    (args: LoaderFunctionArgs): Promise<Response> | Response | Promise<any> | any;
}
Run Code Online (Sandbox Code Playgroud)

新的接口声明和用法:

import {
  RouterProvider,
  createBrowserRouter,
  Navigate,
  useLoaderData,
  LoaderFunction,
  LoaderFunctionArgs
} from "react-router-dom";

interface TodoLoaderFunctionArgs extends Omit<LoaderFunctionArgs, "params"> {
  params: {
    todoId: string;
  };
}

interface TodoLoaderFunction extends Omit<LoaderFunction, "args"> {
  (args: TodoLoaderFunctionArgs):
    | Promise<Response>
    | Response
    | Promise<any>
    | any;
}

const todoLoader: TodoLoaderFunction = async ({ params }) => {
  return await fetchData(params.todoId);
};

const router = createBrowserRouter([
  {
    path: "/todos/:todoId",
    element: <Todo />,
    loader: todoLoader as LoaderFunction
  },
]);
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

编辑react-router-6-how-to-strongly-type-the-params-option-in-route-loader

次要方法#2

另一种可能更简单的选择是简单地重新铸造params道具。

const todoLoader: LoaderFunction = async ({ params }: TodoLoaderArgs) => {
  return params.idTodo ? await fetchData(params.idTodo) : null;
};
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述


小智 5

更简单,这样的事情对我有用:

import type { Params } from "react-router-dom";
export async function loader({ params }: { params: Params<"todoId"> }) {
  return await fetchData(params.todoId);
};
Run Code Online (Sandbox Code Playgroud)

如果您有多个参数,则通用参数是一个联合,例如"todoId" | "userId"