自定义一个hooks
typescript
import { useLocation } from 'react-router-dom';
// 定义 QueryParamsKey 类型为 string 或 Record<string, string>
type QueryParamsKey = string | Record<string, string>;
// 定义返回类型
type QueryParamsResult = Record<string, string>;
const useGetQueryParams = (key: QueryParamsKey): QueryParamsResult => {
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
// 初始化返回结果
let values: QueryParamsResult = {};
// 类型守卫,判断 key 是否为字符串
if (typeof key === 'string') {
values[key] = queryParams.get(key) || '';
} else if (typeof key === 'object' && key !== null) {
// 遍历对象中的每个键,并获取对应的查询参数
Object.keys(key).forEach((k) => {
// 使用 key 中的值作为查询参数的键
values[k] = queryParams.get(key[k]) || '';
});
}
return values;
};
export default useGetQueryParams;
问题:
使用 const {templateId} = useGetQueryParams(['templateId'])
提示类型"QueryParamsResultValues"上不存在属性"templateId"。
解释:
useGetQueryParams
函数的返回类型被定义为 QueryParamsResultValues
,这是一个联合类型 string | Record<string, string>
。当你调用 useGetQueryParams(['templateId'])
时,根据参数类型(在这里是一个数组),返回值应该是一个 Record<string, string>
。
问题是 TypeScript 无法在编译时确定函数的返回类型是 string
还是 Record<string, string>
,因为它依赖于函数的运行时行为。所以,当你尝试解构返回值并访问 templateId
属性时,TypeScript 会抛出错误,因为它不确定返回的是不是一个对象。
解决
使用函数声明式函数重载来让 TypeScript 根据不同的参数类型来推断不同的返回类型
typescript
import { useLocation } from 'react-router';
type QueryParamsKeys = string | string[];
type QueryParamsResult = Record<string, string | null>;
// 函数重载声明
function useGetQueryParams(key: string): string | null;
function useGetQueryParams(keys: string[]): QueryParamsResult;
// 函数实现
function useGetQueryParams(keys: QueryParamsKeys): string | null | QueryParamsResult {
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
// 处理单个键的情况
if (typeof keys === 'string') {
return queryParams.get(keys);
}
// 处理键数组的情况
else if (Array.isArray(keys)) {
const values: QueryParamsResult = {};
keys.forEach((k) => {
values[k] = queryParams.get(k);
});
return values;
}
throw new Error('Invalid keys argument');
}
export default useGetQueryParams;
虽然我们有多个重载签名,但只有一个函数实现。在这个实现内部,我们处理所有可能的调用情况。当函数被调用时,TypeScript 编译器会根据提供的参数类型和重载签名来推断出哪个重载签名被使用,并据此检查返回类型的一致性。
因此,与普通的 JavaScript 不同,TypeScript 的函数重载不是真的在运行时提供多个不同的函数实现,而是在编译时对函数调用的类型进行检查和推断。实际的 JavaScript 代码中,仍然只有一个函数体
注意
在 TypeScript 中,你不能使用箭头函数来进行函数重载。函数重载是 TypeScript 的一项特性,它允许你为同一个函数提供多个函数类型定义。但是,这项特性仅限于传统的函数声明,不适用于箭头函数,因为箭头函数不能被命名。
typescript
// 函数声明,可以进行重载
function greet(name: string): string;
function greet(age: number): string;
function greet(single: boolean): string;
function greet(value: string | number | boolean): string {
// 实际的实现逻辑
return `Hello, ${value}`;
}
这是因为在 JavaScript 运行时,不存在函数重载的概念;它是 TypeScript 在类型系统层面提供的,用于在编译时检查和推断函数调用的类型。函数重载需要一系列具有相同名称的函数声明来实现,而箭头函数是匿名的,因此不能使用重载。
如果你希望在使用箭头函数的同时模拟函数重载的行为,你可以定义一个具有多个签名的类型,并将该类型赋给一个变量,然后将箭头函数赋给这个变量:
typescript
// 定义函数类型
type GreetFunction = {
(name: string): string;
(age: number): string;
(single: boolean): string;
};
// 定义变量并赋予箭头函数,但实际上这不是函数重载
const greet: GreetFunction = (value: string | number | boolean): string => {
// 实际的实现逻辑
return `Hello, ${value}`;
};
上述方法并不是真正的函数重载,只是模拟函数重载的行为。尽管这不是真正的重载,但它仍然可以根据调用时传入的参数类型来返回不同的结果。
为什么箭头函数不支持重载:
- 匿名性质:箭头函数通常是匿名的,它们没有函数名。在 TypeScript 中定义函数重载时,需要多个具有相同名称的声明来表达不同的调用签名。
- 声明方式:在 TypeScript 中,函数重载是通过列出多个具有相同名称的函数声明来实现的。这是通过在类型层面上对同一个函数名提供多个类型签名来完成的。箭头函数作为表达式,不具备这种声明多个类型签名的能力。
为什么在 TypeScript 中使用函数声明进行重载:
- 语法支持:TypeScript 的函数重载语法是专门为传统的函数声明设计的。
- 编译器推断:TypeScript 编译器可以根据重载签名推断出函数调用的返回类型,这为开发者提供了类型安全。