TypeScript速通笔记(上)

前言

大家好!今天我们来一起速通一下 TypeScript(简称 TS)。如果你已经会一点 JavaScript,却总觉得写代码时类型乱飞、报错藏得深、维护起来像在拆盲盒,那么 TypeScript 就是来救你的那个"严格但温柔"的老师。

这份笔记会尽量用大白话把核心概念讲清楚,配上直观的例子,尽可能让各位读完之后能立刻上手。内容完全基于实际使用中的常见知识点,不玩虚的,直接上干货!

一、TypeScript 到底是什么?为什么要学它?

简单来说,TypeScript 就是给 JavaScript 加上了"类型系统"的增强版。我们可以把它想象成:JavaScript 是一个随性的朋友,想怎么写就怎么写;而 TypeScript 是那个会提醒你"这个变量只能放数字哦,别塞字符串进去"的靠谱搭子。

最基础的例子:

ts 复制代码
let a: number;

以后 a 就只能赋值为数字。如果你非要用字符串、布尔值或其他类型给它赋值,TypeScript 会立刻报错,告诉你"不行"。

这一点特别有用。很多时候我们在 JavaScript 里写着写着,某个变量一开始是数字,后面不知不觉变成了字符串,结果运行时才爆炸。TypeScript 把这种问题提前到了写代码阶段。

不过这里有个有趣的现象:即使你的 .ts 文件里有类型错误,你仍然可以用 tsc 命令把它编译成 .js 文件。也就是说,TypeScript 默认是"劝你改,但不强制拦着你"。如果你想让它更严格,必须通过配置文件(比如 tsconfig.json)来设置,以后我们会详细说。现在先记住这个特点就行。

TypeScript 还有一个超级强大的能力:它可以把代码编译成任意版本的 JavaScript。不管是古老的 ES5,还是现代的 ES2020,它都能搞定。这意味着你写的是带类型的"未来代码",最终跑在浏览器或 Node 上的却是兼容性极强的普通 JavaScript。这对项目落地真是太友好啦!

二、类型推断:懒人的福音

TypeScript 很聪明。即使我们声明变量时没有显式写出类型,它也会根据初始值自动推断出类型。一旦推断完成,后面就不能随便换类型了。

举个例子:

ts 复制代码
let b = 10; // TypeScript 自动推断 b 是 number
b = "hello"; // 报错!因为已经推断成数字了

这就是类型推断。它既省事,又保证了后续代码的安全性。我们不需要每个变量都写类型注解,但一旦写了初始值,TypeScript 就会帮我们"锁死"这个类型。

三、函数:从"随便传"到"严格把关"

JavaScript 里的函数对参数个数和类型几乎没什么要求。我们可以传多传少,传什么类型都行,运行时再看运气。

TypeScript 完全不一样。它非常严格:

  • 如果我们指定了参数的个数和类型,就不能多写,也不能少写。
  • 也不能传别的类型的参数。

同时,我们还可以在函数参数括号后面、花括号前面写上返回值类型:

ts 复制代码
function add(a: number, b: number): number {
  return a + b;
}

这样写之后,这个函数就必须接收两个数字,并且必须返回一个数字。少传一个参数、多传一个参数、传字符串进去,或者返回了字符串,都会直接报错。

这种严格性在大型项目中特别有价值。在调用别人写的函数时,编辑器会直接提示我们该传什么,返回值是什么类型,大大减少了"猜"的时间。

四、TypeScript 中的各种类型

TypeScript 的类型系统非常丰富,我们一个一个来看。

1. 字面量类型

我们可以这样写:

ts 复制代码
let a: 10;

这会同时指定 a 的类型和值都是 10。之后我们不能给它赋别的数字,更不能赋其他类型。它就像被"钉死"在 10 这个值上了。

字面量类型特别适合用来表示一组明确的可选值。比如:

ts 复制代码
let direction: "up" | "down" | "left" | "right";

这样 direction 就只能是这四个字符串之一,写其他值会报错。非常适合做状态、方向、模式这类有限选项的场景。

2. any 和 unknown

any 类型是 TypeScript 里最"放飞自我"的类型。一旦一个变量被标成 any,类型检查就对它失效了,跟写原生 JavaScript 几乎没区别。

开发时不建议随便使用 any。因为它让 TypeScript 失去了存在的意义------类型保护。

如果声明变量时既不指定类型,也不给初始值,TypeScript 会隐式把它指定为 any。这也是为什么很多初学者写着写着发现类型检查没了------因为不小心触发了隐式 any。

关于隐式any,之后会专门出一期文章探讨,不要错过哦!

相对地,unknown 才是更推荐的选择。它被称为"类型安全的 any"。

any 类型的变量可以随便赋值给任意类型的变量;而 unknown 类型的变量不能直接赋值给其他类型。我们必须先用 if 判断类型(类型收窄),或者使用类型断言之后才能赋值。

例子:

ts 复制代码
let e: unknown;
let s: string;

// s = e; // 直接赋值会报错
s = e as string; // 类型断言,不报错
// 或者 s = <string>e; 也是另一种写法

unknown 强迫我们认真对待类型,而不是一股脑地用 any 逃避检查。这正是它比 any 更安全的地方。

3. void 和 never

void 表示"空"。以函数为例,如果一个函数的返回值类型是 void,就表示它不具备有意义的返回值。

在 void 函数里,我们可以写 return;、return null; 或者 return undefined;,都不会报错。它主要用来明确表达"这个函数不返回有用的值"。

never 则更极端。它表示"永远不会返回结果",或者"不能是任何值"。通常在函数一定会抛出错误、或者陷入无限循环的情况下,TypeScript 会推断出 never 类型。

never 在类型系统中很有用,尤其是在穷尽性检查(exhaustive check)的场景里,能帮我们确保所有可能的情况都被处理到了。

4. 对象类型

如何限制一个对象的形状呢?可以这样写:

ts 复制代码
let obj: {
  name: string;
  age?: number; // 问号表示可选
  [propName: string]: any; // 后面可以有任意数量、任意类型的属性
};

? 表示这个属性是可选的。[propName: string]: any 是索引签名,意思是对象后面还可以继续添加任意字符串键名、任意类型的属性。

这样既能约束核心字段,又保留了一定的灵活性。

5. 函数类型

我们还可以把函数本身当成一种类型来约束:

ts 复制代码
let d: (a: number, b: number) => number;

这表示 d 必须是一个拥有两个 number 类型参数、并且返回值也是 number 的函数。之后我们可以把符合这个签名的函数赋值给 d,不符合的就会报错。

这种写法在回调函数、高阶函数的场景里特别常见。

6. 数组与元组

数组的类型声明有两种常见写法:

ts 复制代码
let arr1: number[];
let arr2: Array<number>;

两者等价,都表示"数字类型的数组"。其他类型同理,换成 string[]、boolean[] 即可。

元组(tuple)是固定长度的数组。声明方式如下:

ts 复制代码
let a: [string, number, boolean];

这表示 a 必须是长度为 3 的数组,第一个元素是字符串,第二个是数字,第三个是布尔值。顺序和长度都不能乱。

元组和普通数组的区别在于:普通数组长度可变、元素类型相对统一;元组则把每个位置的类型都精确固定下来。

7. 枚举(enum)

枚举用来定义一组有名字的常量:

ts 复制代码
enum Gender {
  Male = 0,
  Female = 1
}

使用时通过 枚举名.枚举值 来访问,比如 Gender.Male。枚举能让代码可读性大幅提升,尤其是在处理状态、性别、角色这类固定选项时。

8. 类型别名(type)与接口(interface)

当类型名特别长、或者需要复用时,可以给它起一个别名:

ts 复制代码
type myType = string | number | boolean;
let a: myType;

type 和 interface 都能给对象指定类型,但它们有明显区别:

  • interface 只能用来给对象声明类型。
  • type 可以给任何类型起别名,包括联合类型、函数类型、基本类型等。

简单记:对象形状优先用 interface,其他复杂类型用 type。

9. 类型断言

有时候咱们比 TypeScript 更清楚某个值的真实类型,这时可以用类型断言来告诉编译器:

ts 复制代码
s = e as string;
// 或者
s = <string>e;

两种写法效果相同。类型断言不是类型转换,它只是告诉 TypeScript"相信我,这就是这个类型",并不会在运行时真正改变值。

五、类型兼容性

在 TypeScript 里,类型兼容性最容易让人一开始觉得"有点反直觉"的地方,就是函数。

只有一句核心规则,我们把它拆开、讲透:

函数中,兼容性是:

少参数函数可赋值给多参数函数 ,

简单类型参数函数可赋值给复杂类型参数函数。

下面咱们用大白话 + 具体例子,把这两条彻底说清楚。

1. 少参数函数可以赋值给多参数函数

先看一个最直观的例子:

ts 复制代码
// 多参数函数类型
type ManyParams = (a: number, b: number) => void;

// 少参数函数
let lessParams = (a: number) => {
  console.log(a);
};

// 这里可以赋值,不会报错
let fn: ManyParams = lessParams;

为什么 TypeScript 允许这样做?

因为在实际调用时,多出来的参数会被直接忽略。

JavaScript 本身就支持"多传参数没事,少传参数可能出问题"。TypeScript 为了兼容这种习惯,采用了参数个数可以少、不能多的兼容规则。

反过来就不行:

ts 复制代码
// 多参数函数
let moreParams = (a: number, b: number) => {
  console.log(a, b);
};

// 少参数函数类型
type FewParams = (a: number) => void;

// 这里会报错!
// let fn2: FewParams = moreParams;

因为如果把一个需要两个参数的函数,塞进只准备传一个参数的位置,调用时就会少传参数,可能导致运行出错。TypeScript 直接在编译阶段拦住了这种情况。

简单记口诀:

"能少不能多" ------ 参数少的函数更"宽松",可以赋值给参数多的函数类型;反过来不行。

2. 简单类型参数函数可赋值给复杂类型参数函数

这里的"简单"和"复杂",指的是参数类型的精确程度。

举个例子:

ts 复制代码
// 复杂类型参数(更具体)
type Complex = (x: { name: string; age: number }) => void;

// 简单类型参数(更宽泛)
let simple = (x: { name: string }) => {
  console.log(x.name);
};

// 可以赋值
let fn: Complex = simple;

为什么可以?

因为当你真正调用 fn 时,传入的一定是带有 name 和 age 的对象。

而 simple 函数只使用了其中的 name,多出来的 age 它根本不关心,也不会出错。所以 TypeScript 认为这是安全的。

反过来同样不行:

ts 复制代码
// 复杂类型参数函数
let complex = (x: { name: string; age: number }) => {
  console.log(x.name, x.age);
};

// 简单类型参数函数类型
type Simple = (x: { name: string }) => void;

// 这里会报错
// let fn2: Simple = complex;

因为如果调用时只传了一个只有 name 的对象,complex 函数内部却要去访问 age,就会出问题。TypeScript 提前拦住了这种风险。

再总结一句:

参数类型越"宽松"(要求越少),就越容易被赋值给要求更严格的函数类型。

为什么 TypeScript 要这样设计?

这两条规则其实都是为了尽量兼容 JavaScript 原本就很灵活的函数调用习惯,同时又不牺牲太多安全性。

JavaScript 里函数参数本来就可以多传少传、类型也管得不严。TypeScript 没有一刀切地强制你必须参数数量和类型完全一模一样,而是选择了"能安全兼容的就放行"的策略。

所以你看到的现象就是:

  • 少参数 → 可以赋给多参数
  • 简单参数类型 → 可以赋给复杂参数类型

理解了这两点,以后看到函数赋值报错或没报错,就能立刻知道 TypeScript 在用什么规则在帮我们做安全检查了。

六、小结与下一步

到这里,我们已经把 TypeScript 最核心、最常用的基础知识过了一遍:

  • 类型注解与类型推断
  • 严格的函数参数与返回值
  • any / unknown / void / never
  • 对象、函数、数组、元组、枚举
  • type 与 interface 的区别
  • 类型断言与字面量类型
  • 基础的类型兼容性

这些内容足以让我们开始用 TypeScript 写更安全、更清晰的代码。记住几个关键原则:

  1. 尽量少用 any,多用 unknown 或具体类型。
  2. 善用类型推断,但关键位置还是显式写出来更清晰。
  3. 遇到报错不要慌,先看 TypeScript 在提醒你什么。

下一篇我们会继续速通,敬请期待《TypeScript速通笔记(下)》!

相关推荐
deli0071 小时前
萤火虫为什么会一起闪:调一个耦合系数,整片林子自己同步
前端
计算机魔术师1 小时前
英伟达联合超100家伙伴推出开放代理安全平台
前端
看谷秀1 小时前
arkts-12 鸿蒙生态/卡片
前端·arkts
老马识码1 小时前
[agent开发面试]上下文工程与压缩:Agent 的稀缺资源
人工智能·面试
朱 欢 庆1 小时前
el-table-v2 虚拟表格组件
前端·javascript·vue.js
时间的拾荒人1 小时前
Qt 信号与槽机制详解(二):连接方式详解与面试指南
开发语言·qt·面试
创新技术阁1 小时前
FastapiAdmin代码生成详细解释
前端·后端·fastapi
杨利杰YJlio1 小时前
ITSK 万能驱动 26V5:新驱动批量更新
前端·javascript·后端
deli0071 小时前
克拉德尼图形:调一下频率,沙子自己排成对称花纹
前端