You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React/Remix自定义Hook中从反序列化函数返回值推导数据类型?

Remix自定义Hook类型推导问题:自动推导反序列化函数返回类型

背景

我们基于Remix开发基础CRUD应用,路由会复用父路由的loader数据,因此封装了自定义Hook useTypedRouteLoaderData,功能和useLoaderData/useRouteLoaderData类似,但loader数据必须经过反序列化才能使用。

最初实现(可行但样板代码过多)

最初的写法能正常工作,但需要在20多个路由中手动编写反序列化逻辑,冗余代码太多:

const data = useTypedRouteLoaderData<typeof otherLoader>('routes/stuff');
const unserializer = (x: typeof data) => {
  const { stuff, popular: _unusedHere, ...rest } = x;

  return {
    ...rest,
    stuff: stuff.map((x) => unserialize<Stuff>(x)),
  };
};
const [stableData, update] = useState(unserializer(data));

useEffect(() => {
  if (data) {
    update(unserializer(data));
  }
}, [data]);

export function useTypedRouteLoaderData<T = any>(
  routeId: string) {
  const unstableRawData = useRouteLoaderData(routeId) as SerializeFrom<T>;

  return unstableRawData;
}

这段代码中stableData的类型是正确的,stuff符合预期,TypeScript会对不存在的属性抛出错误。

优化后的问题

为了减少样板代码,我对Hook做了优化,代码可以正常运行,但rootData的类型变成了any(因为定义了U = any)。我希望U能自动推导为反序列化函数的返回类型,但不知道如何实现:

路由中的使用代码

// routes/somechildroute.tsx

// rootData 类型为 `any`
const [rootData, updateRootData] = useTypedRouteLoaderData<typeof rootLoader>(
  'root',
  ((x) => { // x 的类型是正确的
    return {
      ...x,
      user: x.user ? unserialize<User>(x.user) : null,
    };
  })
);

Hook的实现代码

// hooks/useTypedRouteLoaderData.ts
import { useRouteLoaderData } from '@remix-run/react';
import { SerializeFrom } from '@remix-run/node';
import { useCallback, useState } from 'react';

export type Serialized<T = any> = SerializeFrom<T>;
export type UnserializerFn<T = any, U = any> = (data: Serialized<T>) => U;
export function useTypedRouteLoaderData<T = any, U = any>(
  routeId: string,
  unserializer: UnserializerFn<T, U>
) {
  const unstableRawData = useRouteLoaderData(routeId) as SerializeFrom<T>;

  if (!unstableRawData) {
    if (routeId !== 'root' || !routeId.startsWith('routes')) {
      throw new Error(
        `Invalid routeId ${routeId}: it should be "root" or start with "routes".`
      );
    }

    throw new Error(
      `Could not find loader data for ${routeId}. Ensure that the id is correct, see useMatches().`
    );
  }

  const [data, setData] = useState(unserializer(unstableRawData));

  // 更新为loader返回的最新数据
  const updateData = useCallback(() => {
    setData(unserializer(unstableRawData));
  }, [unstableRawData]);

  return [data, updateData, unstableRawData];
}

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 14:35:01