如何编写具备正确TypeScript属性校验的函数包装器自定义Hook
如何编写具备正确TypeScript属性校验的函数包装器自定义Hook
这个场景太常见了——重复获取level这类公共参数确实会让代码冗余又难维护,你已经找对了方向:用自定义Hook封装公共逻辑,把level自动注入到目标函数里。不过当前的类型问题是因为泛型没有正确处理多个函数的独立类型,导致所有函数的参数被合并到一起了,咱们来一步步修复它。
问题根源分析
你之前的Hook用了T extends any[]作为泛型,这会让TypeScript把传入的多个函数参数当成一个联合类型数组,而不是每个元素独立的元组。所以最终生成的包装函数类型会是所有原函数参数的合并,也就出现了returnFn2要求age、returnFn3要求username的错误。
修复后的自定义Hook
我们需要用元组泛型结合映射类型,让每个传入的函数对应一个独立的包装函数类型,确保每个包装函数只校验自己需要的参数(自动排除level):
import { useGetUserInfo } from './your-path-to-useGetUserInfo'; // 替换成你的实际路径 // 定义泛型:接受一个函数元组,每个函数都接收一个包含level的对象参数 const useWrapperFn = <Fns extends readonly ((props: { level: any; [key: string]: any }) => void)[]>( ...fns: Fns ) => { const { level } = useGetUserInfo(); // 对元组中的每个函数单独处理,生成对应的包装函数 return fns.map((fn) => { return (props: Omit<Parameters<typeof fn>[0], 'level'>) => { fn({ ...props, level }); }; }) as { [K in keyof Fns]: Fns[K] extends (props: infer P) => void ? (props: Omit<P, 'level'>) => void : never; }; }; export default useWrapperFn;
关键类型逻辑解释
- 元组泛型约束:
Fns extends readonly ((props: { level: any; [key: string]: any }) => void)[]确保传入的每个函数都接收一个包含level的对象参数,符合你的业务场景。 - 映射类型生成返回值:
{ [K in keyof Fns]: ... }遍历元组的每个索引,为每个位置的函数单独生成包装类型:- 用
infer P提取原函数的参数类型P - 用
Omit<P, 'level'>去掉level字段,作为包装函数的参数要求
- 用
- 类型断言对齐:把
map返回的数组断言成我们定义的映射类型,让TypeScript正确识别每个包装函数的独立类型。
验证正确用法
现在你的组件里调用时,每个包装函数都会有精准的类型校验:
// Component1 const Component1 = () => { const [returnFn1] = useWrapperFn(fn1); useEffect(() => { returnFn1({ type: 1 }); // ✅ 正常通过校验 // returnFn1(); ❌ TS报错:缺少必填属性type }, []); }; // Component2 const Component2 = () => { const [returnFn2, returnFn3] = useWrapperFn(fn2, fn3); useEffect(() => { returnFn2({ username: 'username' }); // ✅ 正常通过校验,不会要求age // returnFn2({}); ❌ TS报错:缺少必填属性username }, []); useEffect(() => { returnFn3({ age: '27' }); // ✅ 正常通过校验,不会要求username // returnFn3({}); ❌ TS报错:缺少必填属性age }, []); };
这样不仅JavaScript逻辑保持正确,TypeScript的类型校验也能精准对应每个函数的参数要求,完美解决你的问题!
备注:内容来源于stack exchange,提问作者Jake Lam
相关产品推荐
相关产品推荐

