← Back to Index

Chapter 2.2: Functions and Interfaces

TypeScript makes function signatures reliable contracts

⚡ Function Type Annotations

TypeScript allows you to add type annotations to function parameters and return values, making function signatures a clear contract.

Regular function syntax:

function add(a: number, b: number): number {
    return a + b;
}

function greet(name: string): string {
    return `你好,${name}!`;
}

// 返回 void 表示没有返回值
function log(message: string): void {
    console.log(message);
}

Equivalent arrow function syntax:

const add = (a: number, b: number): number => a + b;

const greet = (name: string): string => `你好,${name}!`;

// 多行箭头函数
const processData = (input: string): string[] => {
    const parts = input.split(",");
    return parts.map(p => p.trim());
};

💡 Type Inference:

If the function body is simple, TypeScript can automatically infer the return type. But for public APIs and library functions, explicitly annotating the return type is best practice — it serves as documentation and prevents accidental return type changes.

❓ Optional Parameters and Default Values

Use ? to mark optional parameters, = to set default values, and ...args to collect rest parameters.

// 可选参数:middleName 可以不传
function fullName(first: string, last: string, middleName?: string): string {
    return middleName ? `${first} ${middleName} ${last}` : `${first} ${last}`;
}

fullName("张", "三");           // "张 三"
fullName("张", "三", "小明");   // "张 小明 三"

// 默认值:有默认值的参数自动变为可选
function createUrl(path: string, base: string = "https://api.example.com"): string {
    return `${base}${path}`;
}

createUrl("/users");                     // "https://api.example.com/users"
createUrl("/users", "http://localhost");  // "http://localhost/users"

// 剩余参数:收集任意数量的参数
function sum(...numbers: number[]): number {
    return numbers.reduce((acc, n) => acc + n, 0);
}

sum(1, 2, 3, 4, 5);  // 15

⚠️ Note:

Optional parameters must come after required parameters. If you need a middle parameter to be optional, consider using object parameters or function overloading.

🔀 Function Overloading

When a function returns different types based on different input types, you can use overload signatures to precisely describe this behavior.

// 重载签名(仅声明,不实现)
function parse(input: string): number;
function parse(input: number): string;
// 实现签名(必须兼容所有重载)
function parse(input: string | number): number | string {
    if (typeof input === "string") {
        return parseInt(input, 10);
    }
    return input.toString();
}

const num = parse("42");    // 类型推断为 number
const str = parse(42);      // 类型推断为 string

A more practical example — overloading by parameter count:

function createElement(tag: string): HTMLElement;
function createElement(tag: string, content: string): HTMLElement;
function createElement(tag: string, content?: string): HTMLElement {
    const el = document.createElement(tag);
    if (content) el.textContent = content;
    return el;
}

💡 Overloading vs Union Types:

If there's no correspondence between input/output types, using union types is simpler. Only use overloading when the mapping "input A → returns X, input B → returns Y" needs to be precisely expressed.

📐 interface

Interfaces are used to describe the shape of objects and are one of TypeScript's most core type-building tools.

interface User {
    id: number;
    name: string;
    email: string;
    age?: number;               // 可选属性
    readonly createdAt: Date;   // 只读属性
}

const user: User = {
    id: 1,
    name: "张三",
    email: "zhang@example.com",
    createdAt: new Date(),
};

// user.createdAt = new Date(); // ❌ 编译错误:readonly 不可修改

Index signatures — when you don't know the property names but know the value type:

interface StringMap {
    [key: string]: string;
}

const headers: StringMap = {
    "Content-Type": "application/json",
    "Authorization": "Bearer token123",
};

interface NumberArray {
    [index: number]: string;
    length: number;
}

🔄 Difference from Java:

Java uses interface to define behavioral contracts, and classes must explicitly declare implements. TypeScript's interface is purely structural checking (duck typing) — objects are automatically compatible as long as their structure matches, without any explicit declaration. This means you can define interfaces for third-party library objects without modifying their source code.

🧬 Interface Inheritance and Composition

Interfaces can inherit from other interfaces using extends, supporting multiple inheritance.

interface HasId {
    id: number;
}

interface HasTimestamps {
    createdAt: Date;
    updatedAt: Date;
}

// 继承多个接口
interface User extends HasId, HasTimestamps {
    name: string;
    email: string;
}

// 等价于手写所有属性:
// interface User {
//     id: number;
//     createdAt: Date;
//     updatedAt: Date;
//     name: string;
//     email: string;
// }

const user: User = {
    id: 1,
    name: "李四",
    email: "li@example.com",
    createdAt: new Date(),
    updatedAt: new Date(),
};

💡 Declaration Merging:

Interfaces with the same name automatically merge their properties (declaration merging). This is very useful for extending type definitions of third-party libraries, but can also cause surprises — watch out for naming conflicts.

🏷️ type Aliases

The type keyword can create aliases for any type, more flexible than interface.

// 基本别名
type ID = string | number;
type Status = "active" | "inactive" | "banned";

// 对象类型(与 interface 类似)
type Point = {
    x: number;
    y: number;
};

// 联合类型
type Result<T> = { success: true; data: T } | { success: false; error: string };

// 交叉类型(合并多个类型)
type WithId = { id: number };
type WithName = { name: string };
type Entity = WithId & WithName;  // { id: number; name: string }

// 使用内置工具类型
type PartialUser = Partial<User>;       // 所有属性变为可选
type ReadonlyUser = Readonly<User>;     // 所有属性变为只读
type UserName = Pick<User, "name" | "email">;  // 只取部分属性

🤔 type vs interface: How to choose?

  • interface: Preferred for describing object shapes; supports declaration merging and extends inheritance; better for OOP style
  • type: Union types, intersection types, tuples, and basic type aliases can only use type; more common in functional programming style
  • Mixing both is common in real projects — the key is team consistency

🔗 Function Types and Callbacks

TypeScript can precisely describe the type signatures of callbacks and higher-order functions.

// 用 type 定义函数类型
type Callback = (data: string) => void;
type Predicate<T> = (item: T) => boolean;
type Transformer<T, U> = (input: T) => U;

// 使用函数类型
function fetchData(url: string, onSuccess: Callback, onError: Callback): void {
    // ...
}

// 高阶函数:接收函数,返回函数
function filter<T>(arr: T[], predicate: Predicate<T>): T[] {
    return arr.filter(predicate);
}

const numbers = [1, 2, 3, 4, 5];
const evens = filter(numbers, n => n % 2 === 0);  // [2, 4]

// 工厂函数模式
type Logger = (message: string) => void;

function createLogger(prefix: string): Logger {
    return (message: string) => {
        console.log(`[${prefix}] ${message}`);
    };
}

const appLog = createLogger("APP");
appLog("启动完成");  // "[APP] 启动完成"

💡 Interfaces can also describe functions:

interface SearchFunc {
    (source: string, keyword: string): boolean;
}

const search: SearchFunc = (src, kw) => src.includes(kw);

But in practice, using type to define function types is more concise and more common.

📝 Chapter Summary

Function Types

Parameters and return values can be annotated; supports optional ?, defaults =, and rest parameters ...

Function Overloading

Multiple overload signatures + one implementation; used for precise input-output mapping

interface

Describes object shapes; supports optional/readonly properties, index signatures, inheritance, and declaration merging

type Aliases

More flexible than interface: union types, intersection types, and utility types all require type

Duck Typing

TypeScript's interfaces use structural type checking — no explicit implements declaration needed

Callbacks and Higher-Order Functions

Use type to define function signature types, giving callback parameters full type checking