ts使用函数重载确定返回类型

自定义一个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}`;
};

上述方法并不是真正的函数重载,只是模拟函数重载的行为。尽管这不是真正的重载,但它仍然可以根据调用时传入的参数类型来返回不同的结果。

为什么箭头函数不支持重载:

  1. 匿名性质:箭头函数通常是匿名的,它们没有函数名。在 TypeScript 中定义函数重载时,需要多个具有相同名称的声明来表达不同的调用签名。
  2. 声明方式:在 TypeScript 中,函数重载是通过列出多个具有相同名称的函数声明来实现的。这是通过在类型层面上对同一个函数名提供多个类型签名来完成的。箭头函数作为表达式,不具备这种声明多个类型签名的能力。

为什么在 TypeScript 中使用函数声明进行重载:

  1. 语法支持:TypeScript 的函数重载语法是专门为传统的函数声明设计的。
  2. 编译器推断:TypeScript 编译器可以根据重载签名推断出函数调用的返回类型,这为开发者提供了类型安全。
相关推荐
ch_091817 小时前
从0构建SDK第3节:实现 ReActAgent 的推理与行动循环
typescript·llm·agent
疯狂的魔鬼20 小时前
一套 Schema 驱动四视图:记 useCrudSchemas 的设计与实践
前端·javascript·typescript
kyriewen3 天前
别再对着 TypeScript 报错发呆了:我把 10 个最常见的红色波浪线翻译成了人话
前端·javascript·typescript
妙码生花4 天前
现代前端的极致性能 icon 加载方案(死磕成功版)
前端·vue.js·typescript
MonkeyKing4 天前
鸿蒙ArkTS深度剖析:ArkTS与TS/JS核心差异、静态强类型实战优势
typescript·harmonyos
Momo__6 天前
TypeScript satisfies 操作符——比 as 更安全的类型守门员
前端·typescript
Awu12277 天前
⚡从零开发 Agent CLI(四):给 CLI 装上"LLM 引擎"
typescript·ai编程·claude
假如让我当三天老蒯8 天前
TypeScript 继续学习(学习用)
前端·面试·typescript
糖拌西瓜皮9 天前
Node.js工程化实践:包管理、TypeScript配置与代码质量
typescript·node.js
Bolt11 天前
TypeScript 7.0 来了:当 tsc 用 Go 重写之后
javascript·typescript·go