TypeScript函数:给JavaScript函数加上"类型安全带"

前言

大家好,我是小杨。记得我刚从JavaScript转向TypeScript时,最让我惊喜的就是函数的类型安全特性。以前在JavaScript中,我经常遇到"undefined is not a function"这样的运行时错误,而TypeScript的函数特性就像给代码系上了安全带,让很多错误在编写阶段就能被发现。今天就来聊聊TypeScript中的函数,以及它和JavaScript函数的那些区别。

基础篇:TypeScript函数的基本用法

函数声明的类型化

在JavaScript中,我们这样写函数:

typescript 复制代码
// JavaScript风格
function greet(name) {
    return `Hello, ${name}!`;
}

而在TypeScript中,我们可以为参数和返回值添加类型:

typescript 复制代码
// TypeScript风格 - 为函数系上安全带
function greet(name: string): string {
    return `Hello, ${name}!`;
}

// 使用示例
const message = greet("Alice"); // ✅ 正确
// const errorMessage = greet(123); // ❌ 编译错误:参数类型不匹配

函数表达式与箭头函数

typescript 复制代码
// 函数表达式
const add = function(x: number, y: number): number {
    return x + y;
};

// 箭头函数 - 我的最爱
const multiply = (x: number, y: number): number => x * y;

// 使用类型别名
type MathOperation = (a: number, b: number) => number;

const divide: MathOperation = (a, b) => a / b;

进阶特性:TypeScript函数的超能力

1. 可选参数和默认参数

typescript 复制代码
// 可选参数
function createUser(
    name: string, 
    email: string, 
    age?: number  // 这个问号让参数变成可选的
): User {
    return {
        name,
        email,
        age: age || 0 // 处理可选参数
    };
}

// 默认参数
function sendMessage(
    content: string,
    priority: "low" | "medium" | "high" = "medium", // 默认值
    timeout: number = 5000
): void {
    console.log(`Sending: ${content}, Priority: ${priority}, Timeout: ${timeout}ms`);
}

// 使用示例
createUser("Alice", "alice@example.com"); // ✅ age是可选的
createUser("Bob", "bob@example.com", 25); // ✅ 也可以提供age

sendMessage("Hello"); // ✅ 使用默认参数
sendMessage("Urgent!", "high", 1000); // ✅ 自定义所有参数

2. 剩余参数

typescript 复制代码
// 收集所有参数到一个数组中
function buildPath(...segments: string[]): string {
    return segments.join('/');
}

// 混合使用
function configureApp(
    name: string,
    ...settings: [string, any][]
): AppConfig {
    console.log(`Configuring app: ${name}`);
    settings.forEach(([key, value]) => {
        console.log(`Setting ${key} to ${value}`);
    });
    return { name, settings };
}

// 使用示例
const path = buildPath("usr", "local", "bin", "app"); // "usr/local/bin/app"
configureApp("MyApp", ["theme", "dark"], ["lang", "zh-CN"]);

3. 函数重载

这是TypeScript独有的强大特性:

typescript 复制代码
// 重载签名
function processInput(input: string): string[];
function processInput(input: number): number[];
function processInput(input: boolean): boolean[];

// 实现签名
function processInput(input: any): any[] {
    if (typeof input === 'string') {
        return input.split('');
    } else if (typeof input === 'number') {
        return [input, input * 2, input * 3];
    } else {
        return [input, !input];
    }
}

// 使用示例 - 自动获得正确的类型提示!
const chars = processInput("hello"); // string[] 类型
const numbers = processInput(5);     // number[] 类型
const booleans = processInput(true); // boolean[] 类型

实战对比:TypeScript vs JavaScript函数

场景1:API请求函数

JavaScript版本:

typescript 复制代码
// JavaScript - 容易出错
async function fetchUserData(userId) {
    const response = await fetch(`/api/users/${userId}`);
    const data = await response.json();
    return data; // 返回什么?我们不知道!
}

// 使用时可能会遇到问题
const userData = await fetchUserData(123);
console.log(userData.nonExistentProperty); // 运行时才报错!

TypeScript版本:

typescript 复制代码
// TypeScript - 安全明确
interface User {
    id: number;
    name: string;
    email: string;
    avatar?: string;
}

async function fetchUserData(userId: number): Promise<User> {
    const response = await fetch(`/api/users/${userId}`);
    const data: User = await response.json();
    return data; // 明确的返回类型
}

// 使用时获得完整的类型安全
const userData = await fetchUserData(123);
console.log(userData.name); // ✅ 正确的属性
// console.log(userData.nonExistentProperty); // ❌ 编译时就报错!

场景2:回调函数处理

JavaScript版本:

typescript 复制代码
// JavaScript - 容易出错
function processArray(arr, callback) {
    const result = [];
    for (let i = 0; i < arr.length; i++) {
        result.push(callback(arr[i]));
    }
    return result;
}

// 可能出现的错误
const numbers = [1, 2, 3];
const doubled = processArray(numbers, (num) => num * 2); // 正常工作
const problematic = processArray(numbers, "not a function"); // 运行时崩溃!

TypeScript版本:

typescript 复制代码
// TypeScript - 类型安全
function processArray<T, U>(
    arr: T[],
    callback: (item: T, index: number) => U
): U[] {
    const result: U[] = [];
    for (let i = 0; i < arr.length; i++) {
        result.push(callback(arr[i], i));
    }
    return result;
}

// 使用示例 - 完全类型安全
const numbers = [1, 2, 3];
const doubled = processArray(numbers, (num) => num * 2); // number[] 类型
const strings = processArray(numbers, (num) => num.toString()); // string[] 类型

// const error = processArray(numbers, "not a function"); // ❌ 编译错误

场景3:配置对象处理

JavaScript版本:

typescript 复制代码
// JavaScript - 配置容易出错
function createButton(config) {
    const defaultConfig = {
        text: "Button",
        color: "blue",
        size: "medium",
        disabled: false
    };
    
    return { ...defaultConfig, ...config };
}

// 可能的问题
const button1 = createButton({ text: "Click me", colour: "red" }); // 拼写错误,静默失败
const button2 = createButton({ size: "extra-large" }); // 无效的尺寸,运行时才可能发现

TypeScript版本:

typescript 复制代码
// TypeScript - 配置安全
interface ButtonConfig {
    text?: string;
    color?: "blue" | "red" | "green" | "yellow";
    size?: "small" | "medium" | "large";
    disabled?: boolean;
}

function createButton(config: ButtonConfig) {
    const defaultConfig: Required<ButtonConfig> = {
        text: "Button",
        color: "blue",
        size: "medium",
        disabled: false
    };
    
    return { ...defaultConfig, ...config };
}

// 使用示例 - 即时错误检测
const button1 = createButton({ 
    text: "Click me", 
    color: "red",  // ✅ 有效颜色
    size: "large"  // ✅ 有效尺寸
});

// const button2 = createButton({ colour: "red" }); // ❌ 拼写错误,编译时报错
// const button3 = createButton({ size: "extra-large" }); // ❌ 无效尺寸,编译时报错

高级特性:TypeScript函数的独特能力

1. 泛型函数

typescript 复制代码
// 泛型让函数更加灵活
function identity<T>(value: T): T {
    return value;
}

// 自动类型推断
const num = identity(42);        // number 类型
const str = identity("hello");   // string 类型
const bool = identity(true);     // boolean 类型

// 泛型约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const user = { name: "Alice", age: 30 };
const userName = getProperty(user, "name"); // string 类型
const userAge = getProperty(user, "age");   // number 类型
// const invalid = getProperty(user, "email"); // ❌ 编译错误

2. 条件类型与函数

typescript 复制代码
// 基于输入类型的条件返回
type ApiResponse<T> = T extends number 
    ? { data: number; type: "number" }
    : T extends string
    ? { data: string; type: "string" }
    : { data: T; type: "object" };

function createResponse<T>(data: T): ApiResponse<T> {
    if (typeof data === "number") {
        return { data, type: "number" } as ApiResponse<T>;
    } else if (typeof data === "string") {
        return { data, type: "string" } as ApiResponse<T>;
    } else {
        return { data, type: "object" } as ApiResponse<T>;
    }
}

// 自动推断正确的返回类型
const numResponse = createResponse(42);    // { data: number; type: "number" }
const strResponse = createResponse("hello"); // { data: string; type: "string" }

3. 函数类型的高级用法

typescript 复制代码
// 函数组合
type FunctionType<T, R> = (arg: T) => R;

function compose<T, U, R>(
    f: FunctionType<U, R>,
    g: FunctionType<T, U>
): FunctionType<T, R> {
    return (x: T) => f(g(x));
}

// 使用组合
const addFive = (x: number) => x + 5;
const multiplyByTwo = (x: number) => x * 2;
const toString = (x: number) => x.toString();

const processNumber = compose(toString, compose(addFive, multiplyByTwo));
const result = processNumber(10); // "25"

最佳实践和注意事项

1. 合理使用any和unknown

typescript 复制代码
// 不推荐:过度使用any
function dangerousFunction(data: any): any {
    // 这里可能发生任何事!
    return data.someProperty.someMethod();
}

// 推荐:使用unknown进行类型安全处理
function safeFunction(data: unknown): string {
    if (typeof data === 'string') {
        return data.toUpperCase();
    } else if (data && typeof data === 'object' && 'message' in data) {
        return String(data.message);
    }
    return "Unknown data";
}

2. 充分利用类型推断

typescript 复制代码
// TypeScript可以推断返回类型,不需要总是显式声明
function calculateTotal(price: number, quantity: number) {
    return price * quantity; // 自动推断返回number类型
}

// 但对于复杂逻辑,显式声明更好
function processOrder(order: Order): ProcessResult {
    // 复杂的处理逻辑...
    return result;
}

3. 错误处理的最佳实践

typescript 复制代码
// 使用Result模式而不是抛出错误
type Result<T, E = Error> = 
    | { success: true; data: T }
    | { success: false; error: E };

function safeDivide(a: number, b: number): Result<number> {
    if (b === 0) {
        return { success: false, error: new Error("Division by zero") };
    }
    return { success: true, data: a / b };
}

// 使用示例
const result = safeDivide(10, 2);
if (result.success) {
    console.log(result.data); // 5
} else {
    console.error(result.error.message);
}

总结:TypeScript函数的优势

通过上面的对比和实践,我们可以看到TypeScript函数相比JavaScript函数的主要优势:

  1. 类型安全:在编译时捕获类型错误
  2. 更好的智能提示:IDE可以提供准确的参数和返回类型提示
  3. 自文档化:函数签名本身就是很好的文档
  4. 重构友好:类型系统帮助安全地进行代码重构
  5. 团队协作:明确的接口约定减少沟通成本

结语

从JavaScript的"自由奔放"到TypeScript的"规范有序",函数的类型化可能是最有价值的改进之一。它就像给我们的代码加上了安全带,虽然一开始可能觉得有些束缚,但一旦习惯,就会发现它能避免很多潜在的事故。

记住,好的TypeScript代码不是一味地添加类型,而是找到类型安全和开发效率的最佳平衡点。希望今天的分享能帮助你在实际项目中更好地使用TypeScript函数!

⭐ 写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

相关推荐
许商17 分钟前
【stm32】【printf】
java·前端·stm32
JIngJaneIL27 分钟前
智慧物业|物业管理|基于SprinBoot+vue的智慧物业管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·论文·智慧物业管理系统
爬坑的小白29 分钟前
vue 2.0 路由跳转时新开tab
前端·javascript·vue.js
爬坑的小白30 分钟前
vue x 状态管理
前端·javascript·vue.js
凌览44 分钟前
一键去水印|5 款免费小红书解析工具推荐
前端·javascript·后端
有意义44 分钟前
栈数据结构全解析:从实现原理到 LeetCode 实战
javascript·算法·编程语言
lichong9511 小时前
鸿蒙 web组件开发
前端·typescript
1024小神1 小时前
在html中使用js动态交换两个元素的位置
前端
鹿鹿鹿鹿isNotDefined1 小时前
逐步手写,实现符合 Promise A+ 规范的 Promise
前端·javascript·算法
一千柯橘1 小时前
Electron - IPC 解决主进程和渲染进程之间的通信
前端