TypeScript 入门教程:01 — 为什么需要类型

📅 2026/7/12 5:31:42 👁️ 阅读次数 📝 编程学习
TypeScript 入门教程:01 — 为什么需要类型

1. 你以前怎么惨

你写了一个购物车计算函数。两个参数:商品单价和数量。

// shopping.jsfunctioncalcTotal(price,qty){returnprice*qty;}

同事 A 从 API 拿到价格,是一个字符串"19.90"。他调用calcTotal("19.90", 3)。JS 把字符串悄悄转成数字,得到59.7。碰巧对了。

同事 B 从另一个接口拿到数量,也是字符串"5"。他调用calcTotal(12.5, "5")。又碰巧对了,JS 又帮你转了。

然后同事 C 来了。他在表单里取价格,用户不小心输入了"abc"calcTotal("abc", 3)返回NaN。没有报错,没有红字,购物车总价显示 “NaN”。你翻了几百行代码才找到这一行。

更惨的是:同事 D 把参数顺序写反了——calcTotal(3, 19.90)。JS 照样算出一个数字,只是结果是错的。没人告诉你。

JS 的问题:它太"好心"了。什么东西都帮你悄悄转换,错了也不说。

2. 先看一眼烂代码

下面这些代码在 JS 里全部能跑。没有一个报错。结果全是错的。

// 数字 + 字符串 = 字符串拼接,不是加法"5"+3;// -> "53"(你想要 8)// 数学运算碰上字符串 = NaN,但 JS 不报错Math.max("hello","world");// -> NaN// 访问不存在的属性 = undefined,继续用 undefined 调用方法 = 炸constuser={name:"小明"};user.email.toUpperCase();// ❌ TypeError: Cannot read properties of undefined// 对象拼错字段名 = undefined,没人提醒你functiongetAge(person){returnperson.gae;// 拼错了 age ——返回 undefined}getAge({name:"张三",age:30});// -> undefined(应该是 30)// 数组方法用在字符串上——字符被当成元素遍历"hello".map(ch=>ch.toUpperCase());// ❌ TypeError: "hello".map is not a function

你看出来了吗?每一个错误都是运行到那一行才炸。有些甚至不炸,只是悄悄地错了。你的测试覆盖率要 100% 才能抓到全部问题——现实中没有人做到。

3. 用新语法修好它

3.1 给参数贴标签:stringnumberboolean

JS 的方式:参数叫name,但鬼知道它是什么。

functiongreet(name,age,isMember){return`${name}${age}${isMember}`;}greet(42,"小明","yes");// 全错,不报错

TS 的方式:参数后面加冒号和类型名。就像快递包裹上贴标签。

functiongreet(name:string,age:number,isMember:boolean):string{return`${name}${age}${isMember}`;}greet(42,"小明","yes");// ❌ 编译就报错:第一个参数应该是 string

类型标注就是给每个变量发一张身份证。身份证上的"姓名"栏只能写文字,"年龄"栏只能写数字。填错了,编译器直接拒绝。

三个基本类型:

  • : string— 文字。"hello""张三"
  • : number— 数字。整数、小数、NaNInfinity都是 number。
  • : booleantruefalse。没有第三种。

3.2 描述对象的形状:interface

JS 里对象是"麻袋"——什么东西都能往里塞。

functionprintUser(user){console.log(user.name,user.age);}// 下面三个调用都不报错,但只有第一个有意义:printUser({name:"小明",age:18});// 对的printUser({name:"小明"});// age = undefinedprintUser({nam:"小明",age:18,xxx:"垃圾"});// name 拼错 + 多余字段

TS 用interface描述对象的"形状"——有哪些字段,各自什么类型。

interfaceUser{name:string;age:number;}functionprintUser(user:User):void{console.log(user.name,user.age);// TS 知道 user 一定有 name 和 age}printUser({name:"小明",age:18});// ✅// printUser({ name: "小明" }); // ❌ 缺 age// printUser({ name: "小明", age: 18, xxx: 1 }); // ❌ 多余字段

interface就像一张表格模板。你定义了三个列:姓名、年龄、邮箱。任何人填表时必须每列都填,不能多填一列,也不能少填一列。

类比:interface= 快递单模板。"收件人"栏、"电话"栏、"地址"栏——缺一不可,不能多贴一张便利贴。

3.3 可选字段:?

现实中的表单不会每个字段都必填。邮箱可能没有,昵称可能不写。

interfaceProfile{name:string;// 必填email?:string;// 可选:有就填,没有就不填}functionshowProfile(p:Profile):string{// TS 强制你处理 email 可能是 undefined 的情况return`${p.name}(${p.email??"无邮箱"})`;}showProfile({name:"小明"});// ✅ 不填 email 也可以showProfile({name:"小红",email:"hi@x.com"});// ✅ 填了更好

?= 表单上的"(选填)"标记。你不填它,值是undefined。TS 知道它可能是undefined,所以你不先判断就不能直接用(比如直接.toUpperCase()会报编译错)。

3.4 数组与字面量类型

数组number[]表示"这个篮子里只能装苹果(数字)"。不能塞一根香蕉(字符串)。

functionsum(nums:number[]):number{returnnums.reduce((a,b)=>a+b,0);}sum([1,2,3]);// ✅// sum(["1", "2"]); // ❌ 字符串不能放进 number[]

字面量类型:一个变量不只是string,它只能是"dark""light"

// JS:theme 是 string,拼错了也不报错functionsetTheme(theme){if(theme==="dark"){/* ... */}}setTheme("drak");// 悄悄错了
// TS:theme 只能是这两个值之一typeTheme="dark"|"light";functionsetTheme(theme:Theme):void{/* ... */}// setTheme("drak"); // ❌ 编译报错:"drak" 不在 "dark" | "light" 中

|读作"或"。"dark" | "light"= 只能是 dark 或 light。

as const:像超级胶水,让 TS 把值当类型用,而不是宽泛的string/number

constcfg={theme:"dark",port:3000}asconst;// cfg.theme 类型 = "dark"(不是 string)// cfg.port 类型 = 3000(不是 number)// 整个对象变成只读,不可修改

4. 什么时候不该用

不要给一个 10 行的临时脚本加上全套interface。类型标注适合"会被多次调用"、“有其他人维护”、"数据流过多个函数"的场景。

  • 写完就扔的脚本:别加类型,浪费时间。
  • JSON 数据本身结构不明:比如解析一个你完全没见过的第三方 API 返回。先用unknown而不是硬猜一个 interface。
  • as const不要用在"用户会修改"的数据上。配置常量用它,表单输入别用。

5. 小结

语法一句话示例
: string这个变量是文字name: string
: number这个变量是数字age: number
: boolean这个变量是 true/falsedone: boolean
interface描述对象长什么样interface User { name: string }
?这个字段可以不填email?: string
number[]数字组成的数组scores: number[]
"a" | "b"值只能是这几个之一dir: "left" | "right"
as const冻结值,让 TS 知道精确值{ port: 3000 } as const