一、为什么类型不匹配让人头疼

写TypeScript的时候,最常遇到的错误之一就是类型不匹配。比如你定义了一个函数,期望接收一个数字,结果调用时传了个字符串,这时候TypeScript就会毫不留情地报错。这种错误看似简单,但在实际开发中,尤其是项目逐渐复杂后,类型不匹配的问题可能会变得非常隐蔽,甚至导致运行时错误。

举个例子:

// 定义一个计算平方的函数
function square(num: number): number {
  return num * num;
}

// 调用时传入了字符串
square("10"); // 错误:Argument of type 'string' is not assignable to parameter of type 'number'.

这个例子很简单,一眼就能看出问题。但如果是在一个大型项目中,类型不匹配可能隐藏在多层嵌套的对象或复杂的接口定义里,这时候排查起来就费劲了。

二、常见的类型不匹配场景

1. 对象属性类型不匹配

在TypeScript里,对象的属性类型必须严格匹配,否则就会报错。比如:

interface User {
  id: number;
  name: string;
}

// 定义一个用户对象
const user: User = {
  id: "123", // 错误:Type 'string' is not assignable to type 'number'.
  name: "张三",
};

这里id应该是number类型,但赋值时却给了字符串,TypeScript会直接报错。

2. 函数参数类型不匹配

函数参数的类型必须和定义一致,否则调用时会报错:

function greet(name: string): void {
  console.log(`Hello, ${name}!`);
}

greet(123); // 错误:Argument of type 'number' is not assignable to parameter of type 'string'.

3. 数组元素类型不匹配

如果定义了一个特定类型的数组,往里面放其他类型的元素就会报错:

const numbers: number[] = [1, 2, 3];
numbers.push("4"); // 错误:Argument of type 'string' is not assignable to parameter of type 'number'.

4. 联合类型的不匹配

联合类型允许变量是多种类型之一,但如果赋值时不符合其中任何一种,仍然会报错:

let age: number | string;
age = 25; // 合法
age = "25"; // 合法
age = true; // 错误:Type 'boolean' is not assignable to type 'string | number'.

三、解决类型不匹配的方法

1. 显式类型转换

有时候我们明确知道某个值的类型,但TypeScript无法自动推断,这时可以用类型断言:

const input: unknown = "123";
const num: number = (input as string).length; // 先断言为string,再取length

不过要注意,滥用类型断言可能会掩盖真正的类型问题。

2. 类型守卫

类型守卫可以帮助TypeScript在特定代码块内缩小变量的类型范围:

function printId(id: number | string) {
  if (typeof id === "string") {
    console.log(id.toUpperCase()); // 这里id一定是string
  } else {
    console.log(id.toFixed(2)); // 这里id一定是number
  }
}

3. 使用泛型

泛型可以让函数或类适应多种类型,同时保持类型安全:

function identity<T>(arg: T): T {
  return arg;
}

const num = identity(123); // T推断为number
const str = identity("hello"); // T推断为string

4. 调整接口定义

如果发现某个接口的类型定义过于严格,可以适当放宽:

interface Config {
  port: number;
  host: string;
  timeout?: number; // 可选属性
}

四、实际开发中的注意事项

  1. 不要滥用any:虽然any能解决类型报错,但会失去TypeScript的类型检查优势。
  2. 优先使用联合类型和泛型:它们能在保证类型安全的同时提供灵活性。
  3. 善用类型推断:TypeScript的类型推断很强大,很多时候不需要手动声明类型。
  4. 定期检查第三方库的类型定义:有些库的类型定义可能不准确,需要手动调整或补充。

五、总结

TypeScript的类型系统虽然严格,但正是这种严格性帮助我们避免了大量潜在的错误。遇到类型不匹配的问题时,不要急于用any糊弄过去,而是应该思考如何正确地定义类型。通过类型守卫、泛型、联合类型等特性,我们可以写出既灵活又安全的代码。