如何在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
相关产品推荐
相关产品推荐

