React router V6.5:如何强类型数据加载器?

Jer*_*me 8 typescript react-router-dom dataloader

使用 React-Router V6,我尝试强类型化正在使用的数据加载器函数params以及useLoaderData钩子。

到目前为止,我必须做以下难看的事情:

A- 对于useLoaderData,需要强制 returnType :

const profil = useLoaderData() as TProfil;
Run Code Online (Sandbox Code Playgroud)

export declare function useLoaderData<T>(): T;我想创建一个通用的钩子而不是导出会更干净declare function useLoaderData(): unknown;

B-对于数据加载器,接收到的参数的类型是什么?不得不强迫any,但这太丑了。如何强类型化并在某处声明 params 由来自路由定义中的参数名称的“id”组成?

const careerDetailDataLoader = async ({ params }: any): Promise<TProfil> => {
  const { id } = params;
  const res = await fetch(`http://localhost:4000/careers/${id}`);

  const data: TProfil = await res.json();
  return data;
};

<Route path=":id" element={<CareerDetailsPage />} loader={careerDetailDataLoader} />
Run Code Online (Sandbox Code Playgroud)

小智 15

昨晚我第一次使用延迟加载器时遇到了和你一样的问题。

我想出了以下辅助函数作为使用时缺乏类型安全的解决方法react-router-dom

// utils.ts
import { Await as RrdAwait, defer, LoaderFunctionArgs, useLoaderData as useRrdLoaderData } from "react-router-dom";

export function useLoaderData<TLoader extends ReturnType<typeof deferredLoader>>() {
    return useRrdLoaderData() as ReturnType<TLoader>["data"];
}

export function deferredLoader<TData extends Record<string, unknown>>(dataFunc: (args: LoaderFunctionArgs) => TData) {
    return (args: LoaderFunctionArgs) =>
        defer(dataFunc(args)) as Omit<ReturnType<typeof defer>, "data"> & { data: TData };
}

export interface AwaitResolveRenderFunction<T> {
    (data: Awaited<T>): React.ReactElement;
}

export interface AwaitProps<T> {
    children: React.ReactNode | AwaitResolveRenderFunction<T>;
    errorElement?: React.ReactNode;
    resolve: Promise<T>;
}

export function Await<T>(props: AwaitProps<T>): JSX.Element {
    return RrdAwait(props);
}
Run Code Online (Sandbox Code Playgroud)

用法:

// MainLayout.tsx
import { Await, deferredLoader, useLoaderData } from "./utils";

export const mainLayoutLoader = deferredLoader(args => ({
    metrics: api.metrics.get()
}));

export const MainLayout: FC = () => {
    const data = useLoaderData<typeof mainLayoutLoader>();

    return (
        <Suspense fallback={<SiderMenu />}>
            <Await resolve={data.metrics}>
                {metrics => <SiderMenu metrics={metrics} />}
            </Await>
        </Suspense>
    );
};
Run Code Online (Sandbox Code Playgroud)
const router = createBrowserRouter([
    {
        path: "/",
        element: <MainLayout />,
        loader: mainLayoutLoader
    }]);
Run Code Online (Sandbox Code Playgroud)

在哪里:

  • api.metrics.get()返回一个类型Promise<Metric[]>
  • useLoaderData<typeof mainLayoutLoader>()返回一个类型{ metrics: Promise<Metric[]>; }
  • metrics{metrics => ...}节点内部将<Await>被键入为Metric[]
  • argsLoaderFunctionArgs是中定义的类型,其结果是forreact-router-dom的类型{ [key: string]: string | undefined; }args.params


iro*_*on9 0

第一个问题

我想创建一个像导出声明函数 useLoaderData(): T; 这样的通用钩子会更干净。而不是导出声明函数 useLoaderData():unknown;

任何使用泛型的解决方案都只会隐藏必要的类型转换,as因此泛型版本会给人一种类型安全的错误感觉。

Remix完全具有您建议的通用性useLoaderData<T>()。在remix github repo中,详细解释了为什么他们弃用该函数的通用版本以及为什么useLoaderData()他们移植到 React-router v6 的函数不使用泛型。(另一个答案中建议的辅助函数与 remix 中已弃用的函数具有相同的问题。)

第二个问题

对于数据加载器,收到的参数类型是什么?

在 中react-router,有ActionFunctionArgs导致Params被定义(在 中@remix-run/router/dist/utils.d.ts/Params)如下:

export type Params<Key extends string = string> = {
    readonly [key in Key]: string | undefined;
};
Run Code Online (Sandbox Code Playgroud)

但是,您可能想查看/sf/answers/5360570911/ ,了解有关 v6的参数类型loader和函数的类似问题(可能还有更清晰的解决方案) 。actionreact-router