mcpField
F
function-type-expressions

function-type-expressions

github.com/marius-townhouse开发644 次安装38 周前更新
代码仓库

关于 function-type-expressions

这项 TypeScript 技能介绍如何为整个函数表达式指定类型,减少重复标注并检查返回值。适用于编写回调、包装函数或签名相同的多个函数。

主要功能

  1. 01为整个函数表达式指定类型
  2. 02在相同签名间复用类型
  3. 03从函数类型推断参数类型
  4. 04按类型检查返回值
  5. 05用 typeof 匹配现有签名

适用场景

  1. 01为库提供的回调添加类型
  2. 02实现签名相同的多个函数
  3. 03包装函数并保持其参数签名
  4. 04减少相关函数中的重复标注

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill function-type-expressions

安装后在 Claude Code 中输入/function-type-expressions确认已加载。

详细介绍

尽可能为整个函数表达式应用类型

概述

一次为整个函数指定类型,而不是分别为各个参数指定类型。

使用函数表达式(而非函数语句)时,可以为整个函数应用类型。这样可以减少重复、提高类型安全性,并让代码更易读。

何时使用此技能

  • 编写多个签名相同的函数
  • 为库实现回调
  • 与现有函数的签名保持一致
  • 包装或扩展现有函数

铁律

When functions share a signature, define the type ONCE and apply it to EACH function.

请记住:

  • 可以为函数表达式应用类型
  • 参数类型会根据函数类型推断
  • 返回类型会根据函数类型进行检查
  • 使用 typeof fn 匹配现有函数签名

检测:重复的签名

// Repetitive - same signature 4 times
function add(a: number, b: number) { return a + b; }
function sub(a: number, b: number) { return a - b; }
function mul(a: number, b: number) { return a * b; }
function div(a: number, b: number) { return a / b; }

解决方案:函数类型

定义一次,多次使用
type BinaryFn = (a: number, b: number) => number;

const add: BinaryFn = (a, b) => a + b;  // Types inferred
const sub: BinaryFn = (a, b) => a - b;
const mul: BinaryFn = (a, b) => a * b;
const div: BinaryFn = (a, b) => a / b;

优点:

  • 无需重复标注类型
  • 自动检查返回类型
  • 没有类型噪声,逻辑更加清晰
使用 typeof 匹配现有函数
// Match fetch's signature exactly
const checkedFetch: typeof fetch = async (input, init) => {
  const response = await fetch(input, init);
  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }
  return response;
};

TypeScript 会推断:

  • input: RequestInfo | URL
  • init?: RequestInit
  • 返回值:Promise<Response>
在保留参数的同时更改返回类型
// Match parameters but change return type
async function fetchNumber(
  ...args: Parameters<typeof fetch>
): Promise<number> {
  const response = await checkedFetch(...args);
  return Number(await response.text());
}

函数语句与函数表达式

// Statement - must annotate each parameter
function rollDice1(sides: number): number { /* ... */ }

// Expression - can apply type to entire function
type DiceRollFn = (sides: number) => number;
const rollDice2: DiceRollFn = (sides) => { /* ... */ };

常见函数类型模式

回调类型
type EventHandler = (event: Event) => void;
type AsyncCallback<T> = () => Promise<T>;
type Comparator<T> = (a: T, b: T) => number;

const handleClick: EventHandler = (e) => {
  console.log(e.target);  // e is typed as Event
};
泛型函数类型
type Mapper<T, U> = (item: T, index: number) => U;

const double: Mapper<number, number> = (n) => n * 2;
const stringify: Mapper<number, string> = (n) => String(n);
接口语法(替代方案)
// Function type as interface
interface StringTransform {
  (input: string): string;
}

const toUpper: StringTransform = s => s.toUpperCase();

返回类型安全性

函数类型可以捕获返回类型错误:

const checkedFetch: typeof fetch = async (input, init) => {
  const response = await fetch(input, init);
  if (!response.ok) {
    return new Error('Failed');  // Error!
    // Type 'Error' is not assignable to type 'Response'
  }
  return response;
};

如果没有函数类型,只有在调用处才会报错。

库的回调类型

库通常会提供回调类型:

// React provides these
import { MouseEventHandler, ChangeEventHandler } from 'react';

const handleClick: MouseEventHandler<HTMLButtonElement> = (e) => {
  console.log(e.currentTarget.disabled);  // Fully typed
};

const handleChange: ChangeEventHandler<HTMLInputElement> = (e) => {
  console.log(e.target.value);  // Fully typed
};

何时不使用函数类型

不要为单个函数过度设计:

// Overkill for a single standalone function
type GreetFn = (name: string) => string;
const greet: GreetFn = (name) => `Hello, ${name}`;

// Just use a normal function statement
function greet(name: string): string {
  return `Hello, ${name}`;
}

抗压协议

1. “函数语句更清晰”

压力:“我更喜欢将类型直接写在参数旁边。”

**回应:**对于共享签名的函数,集中定义类型可以减少噪声。

**行动:**当有 2 个或更多函数共享签名时,使用函数类型。

2. “我不知道库的类型”

压力:“我找不到库中的回调类型。”

**回应:**使用 typeof existingFunction 或 Parameters<typeof fn>。

**行动:**使用 typeof 匹配现有签名。

危险信号 - 停下来重新考虑

  • 多个函数重复使用相同的参数类型
  • 应与被包装函数签名保持一致的包装函数
  • 未正确指定类型的回调

常见借口(都不成立)

借口实际情况
“参数上的类型更清晰”重复 4 次时就不清晰了
“函数语句更简单”带类型的函数表达式同样清晰
“我就用 any”会失去所有类型安全性优势

快速参考

// Define function type
type Transform<T> = (value: T) => T;

// Apply to function expression
const double: Transform<number> = v => v * 2;

// Match existing function
const myFetch: typeof fetch = async (input, init) => { ... };

// Match parameters, change return
function wrapper(...args: Parameters<typeof original>): NewReturn { ... }

归根结底

当多个函数共享签名时,为整个函数表达式应用类型。

这样可以减少重复、集中定义类型,并在源头捕获返回类型错误。使用 typeof 精确匹配现有函数签名。

参考

基于 Dan Vanderkam 所著的《Effective TypeScript》,第 12 条:尽可能为整个函数表达式应用类型。

评论

支持 Markdown