注解入门 — 基本类型、对象字面量、可选属性与类型别名¶
这一章讲四件事: 冒号语法和第一批类型词汇;「什么时候要注解、什么时候让编译器自己猜」的分工规则; 怎么描述对象的形状(包括「某个属性可以有可以没有」);以及怎么给类型起名字、跨文件复用。 读完你会写出第一批真正的 TypeScript,并拿到全书使用频率最高的一条规则。
1. 顶层全景:两条线的分工¶
注解(你写) 推断(它猜)
│ │
函数参数 ——必须———————→ 变量 ——通常不用
│ │
「编译器看不到调用方」 「赋值就在那里,看一眼就知道」
图说:这张分工表是本章的主线。下面每个机制都在回答「这条线这边怎么办」。
2. 冒号与七种基本类型¶
注解(type annotation)就是告诉 TypeScript「这个东西应该是什么类型」,最常见的写法是冒号:
名字后面跟 : 类型1。先认识第一批词汇——对应 JavaScript 七种基本值:
let example1: string = "Hello World!";
let example2: number = 42;
let example3: boolean = true;
let example4: symbol = Symbol();
let example5: bigint = 123n;
let example6: null = null;
let example7: undefined = undefined;
前三个(string 字符串、number 数字、boolean 布尔)天天见。 后四个多说一句:
- symbol(符号):JavaScript 里用来保证属性键唯一的一种值,平时很少直接写它的类型2。
- bigint(大整数):JavaScript 的普通数字有个安全上限(约九千万亿,
Number.MAX_SAFE_INTEGER),超过它加减会算错;bigint 就是为「真正大的整数」准备的,字面量带个n后缀(如123n)3。 - null 和 undefined 各是一个类型:分别只有一个值。JavaScript 里「没有」的两种写法,在类型系统里是两张独立的面孔——这个区分第 06 章还要回来讲。
一个重要提醒:这些基本类型「知道就好,日常很少用来标注变量」——因为推断会替你做(下一节)。 它们真正的用武之地是函数参数。
3. 主线例子:一个函数的三次修正¶
本章的主线例子是一个登记唱片信息的函数。它贯穿注解、推断、any 三个机制,从头走一遍。
第一版:参数注解。
三个参数各带一个注解。注解的回报是调用检查:你传错顺序试试——
false 占了第二个位置,而第二个位置要的是 number——红线先画它;
你把 false 换成 20,红线挪到 15 上,因为第三个位置要的是 boolean4。
注意报错里的关键词 assignable(可赋值):TypeScript 说「某某 is not assignable to 某某」,
意思就是「这个东西放不进那个位置」。这句话会陪伴你整本书。
第二版:变量注解——然后发现可以省掉。
let albumTitle: string = "Midnights";
albumTitle = "1989"; // 没问题
let isReleased: boolean = true;
isReleased = "yes"; // 红线:Type 'string' is not assignable to type 'boolean'.
标注过的变量,TypeScript 保证它后来赋的值始终兼容5。但现在把注解全删掉试试:
一个错误都没有。悬停三个变量,类型端端正正地在那里:string、boolean、number6。
这就是类型推断(type inference):TypeScript 看着赋值,自己猜出了类型;
而且猜完之后一切照旧——把 true 换成 "yes" 照样报错,
albumTitle 上敲 .toUpper 照样补全出 toUpperCase7。
原书把这条的份量说得很足:「你可以基本不给变量注解,IDE 照样知道所有东西的类型」—— 这是 TypeScript 极其强大的一部分8。
第三版:把参数注解删掉——这次不行。
const logAlbumInfo = (
title, // 报错:Parameter 'title' implicitly has an 'any' type.
trackCount, // 同上
isReleased, // 同上
) => { /* ... */ };
为什么变量能猜、参数不能?原书给的解释一针见血:
变量有赋值摆在那里,函数参数没有。 参数只是一个占位名,函数体里 a + b 中的 a、b
可以是字符串、布尔或任何东西——从函数自身看不出它「应该」是什么9。
由此得到全书使用频率最高的规则:
函数参数必须注解;变量通常不用。
规则有一个聪明的例外:把函数传给 .map 时,回调的参数不用标——
因为 TypeScript 从「这是对字符串数组 map」已经能推出每次进来的元素是字符串10。
记住这个例外的形状就行:当类型能从上下文流进来时,参数也可以不标。
(「上下文推断」是第 16、17 章泛型的主角,这里是它第一次露头。)
顺带一笔:推断还能省更多。 练习里把 add(a: boolean, b: boolean) 改成
add(a: number, b: number) 后,不仅调用错误消失了——返回值也被自动推断成 number11。
将来从另一个语言来的读者会想写 int / float:TypeScript 没有这些,
数字只有一个 number12。注解返回值这件事,全书的态度是:让推断干。
4. any:关掉整个类型系统的逃生门¶
上面第三版报错里的那个词,现在正面介绍。当 TypeScript 不知道一个东西的类型时,它会给它 any。
any 是类型系统的「关闭开关」:标成 any 的值,任何东西都能赋给它、它的任何属性都能访问、
还能当函数调用——全部不报错13。
第一行运行到就炸——但 TypeScript 一声不吭。13
any 不是魔鬼:类型复杂到描述不了时、把老 JavaScript 项目迁到 TypeScript 时,
它是可用的逃生门(escape hatch)14。但原书用练习演示了它的真实代价,值得转述:
一个表单处理函数把参数标成 any,代码里藏着一处拼写错误(e.terget,应为 e.target),
编辑器全程安静——因为 any 不但关掉检查,连自动补全也一起关了,
没有了补全,拼错属性这种最typical的错误就再也拦不住15。
判断(我们的,不是书里的): 这一章把 any 讲得比多数入门材料诚实—— 它没有只说「别用 any」,而是让你看见 any 的具体损失函数:检查 + 补全 + 重构,三样一起没。 实用的推论是:嫌类型麻烦而标 any 的地方,往往正是类型最能帮上忙的地方(边界数据、遗留代码)。 如果错,会错在: 有些场景(快速原型、一次性脚本)any 的损失确实接近零, 这条推论对那些场景是过度紧张。
5. 描述对象的形状:对象字面量类型¶
解决什么问题: 函数参数常常不是一个简单值,而是一个对象。怎么描述「这个对象应该长什么样」?
参数后面那对大括号不是在创建一个对象——它是在描述一个形状:
这个对象要有 name(字符串)、type(字符串)、age(数字)三个属性。
这个语法有个正式名字:对象字面量类型(object literal type)16。
对象类型的回报立刻兑现:调用函数时,补全列表会直接给出三个属性名, 属性多、名字像的时候,这既省时间又防拼错17。
可选属性:?,以及它和 | undefined 的关系¶
解决什么问题: 不是每个属性都必填。把 age 标成可选的,在属性名后加一个问号:
调用时就可以不给 age18。这个小小的 ? 背后藏着本章最值得悬停一次的地方:
在函数体里把鼠标悬停到 user.last 上,显示的既不是 string 也不是「可能没有」,
而是 string | undefined——竖线读作「或」,意思是「字符串,或者 undefined(没有)」19。
? 在类型层面的全部含义就是「这个属性可能存在、可能不存在,不存在时取出来是 undefined」。
(竖线并集是第 06 章的主角,这里先混个脸熟。)
练习里那道「报错说缺属性、但其实你是对的」的题值得记住报错原文:
当你少传了一个必填属性,TypeScript 会说
Property 'last' is missing in type ... but required in type ...20——
missing(缺)+ required(必填),这两个词出现,就是该加 ? 或者该补值的信号。
6. 给类型起名字:type 关键字¶
解决什么问题: 同一个形状在应用里反复出现。把 {name: string; type: string; age?: number}
在十个文件里抄十遍,改一处就要改十处。
type 关键字用来给一个类型起名字,起出来的名字叫类型别名(type alias):
从此 Animal 可以出现在任何类型该出现的位置:变量注解、函数参数、返回值21。
类型别名不只可以指对象——type Id = string | number; 也是合法的别名,
含义是「Id 可以是字符串或数字」22。
原书给了 type 别名一个很高的工程定位,这句话值得原样记住:
用类型别名是「保证类型定义单一事实源」的好办法,让未来的修改更容易23。
练习的对照很直观:两个函数都收 {width, height} 矩形,抽成 type Rectangle 之后,
函数签名从三行塌缩成一行,而「宽和高是什么」这个事实从此只有一个出处24。
7. 跨文件共享类型¶
类型别名可以住在自己的文件里,谁用谁导入:
// shared-types.ts
export type Animal = {
name: string;
type: string;
};
// index.ts
import type {Animal} from "./shared-types";
两个细节:
export把类型放出文件;import type明确声明「我导入的只是类型,不是运行时的值」—— 这个区分在第 14 章(模块)会显出意义25。- 书里提了个约定:可以专门建
.types.ts文件,把类型定义和其他代码分开存放26。
到这一步,主线例子的完整形态就出来了: 参数用类型别名标形状 → 调用处补全属性 → 传错类型当场红线 → 编译时别名连同所有注解一起被擦除。 类型的全部作用仍然发生在「写的时候」——这正是第 03 章那条原则的第一个完整样本。
8. 四个练习走查:注解的第一次实战¶
原书本章的练习每个都瞄准一个具体误区,值得逐个走一遍。
练习 4-1:别用 int 和 float(第 2 节的预演)¶
一个 add 函数,参数被错标成 boolean:add(a: boolean, b: boolean)。
从别的语言转过来的直觉是「那就换成 int 或 float」——TypeScript 没有这两个类型,
数字只有一个 number(整数小数通吃)28:
function add(a: number, b: number) {
return a + b;
}
add("something", 2); // ❌ string is not assignable to number
const result = add(1, 2); // ✅ 悬停 result → number
改对之后的两个白捡收益值得记住:传错类型的调用当场报错; 返回值不用标注就被推断成 number——TypeScript 不只推断变量,也推断函数返回类型28。
练习 4-2:参数注解是最没有商量余地的一类¶
concatTwoStrings(a, b) 两个裸参数,双双「implicitly has an 'any' type」。
解法没有任何花样:(a: string, b: string)29。
这道题的要点不在解法,而在它和其他练习题的对照:返回值可以永远不标(推断兜底),
参数却几乎必须标——同一道题里,出题人把两类注解的待遇差摆在了明面上。
练习 4-3:七种基本类型的连珠报错¶
五个变量全部错标成 string,悬停逐个给出
Type 'number' is not assignable to type 'string' 一类的报错,
改回各自的正确类型(string/number/boolean/symbol/bigint)即过30。
这道题真正的教学点在题外:原书紧接着把五个注解全删掉——一个错误都没了,
因为推断对每个值都给出了正确的类型31。
「基本类型值得认识,即使你并不总需要拿它们标注变量」——这就是第 2 节
「变量通常不用注解」的练习版证明。
练习 4-4:any 关掉的不只是报错,还有补全¶
这道题是全书 any 讨论里最锋利的一次演示。一个表单处理函数, 参数标了 any,代码里藏着一个拼写错误:
const handleFormData = (e: any) => {
e.preventDefault();
const data = new FormData(e.terget); // ← 拼错了!target 写成了 terget
const value = Object.fromEntries(data.entries());
return value;
};
编译器全程无红线;测试一跑,炸32。谜底:e.terget 是个不存在的属性—— 如果 e 有正经类型,这一行立刻报错;正因为它是 any, TypeScript 连「这个属性不存在」都无权告诉你。 原书把损失清单说得具体:any 不止关掉类型检查,还关掉了自动补全—— 而补全本来是能当场拦住这个笔误的33。
any 的完整损失清单(比「不报错」长):
1. 类型报错消失 —— 错误流进运行时
2. 自动补全消失 —— 拼写错误当场没人拦
3. 悬停信息变成 any —— 读代码的人失去地图
图说:标 any 省掉的是注解的那几秒,欠下的是上面三样。练习 4-4 的
笔误只是清单第 2 条的一次现场抓获[^35]。
这道题还埋了个后话:原书说「以后会回到这个例子,看正确的类型怎么写」33—— 这个伏笔在第 05 章(JSON.parse 永远返回 any)和第 12 章(satisfies) 都会兑现,any 的正确替代品不是「另一个 any」。
9. 作者的判断与证据(分开标)¶
书里有据的:
- 「参数必须注解、变量不用」——由「变量有赋值可看、参数没有」这个机制解释支撑9,并给出了
.map这一可验证的例外10。 - 「any 关掉的不仅是检查还有补全」——用带拼写错误的练习代码实证:红线缺席、测试运行时才炸15。
- 「类型别名 = 单一事实源」——工程原则主张,由「两个函数共享 Rectangle」的练习展示收益24。
原书没说的(我们补):
- 「推断优先、注解兜底」与工业界其他静态语言(Java、C#)「处处显式」风格的对比—— 补充(不在书里,来自通用知识): 这是「渐进类型」语言共有的实用主义姿态; TypeScript 的编译器默认严格度(第 15 章)让你自己决定站到「显式」还是「简洁」那一端。
- 练习里用到的
Expect/Equal类型级断言是 totaltypescript 练习库的工具,原书一笔带过27—— 它们的原理(用类型层面做相等检查)属第 16 章的条件类型领域。
10. 边界与局限¶
- 基本类型清单不完整:
object、unknown、never等基本类型本章没出场(unknown 第 06、13 章,never 第 06 章)。 - any 只是初见:第 13 章会讲一个 any 的「变异体」evolving any,比这里的 any 微妙得多。
- 变量注解并非永远多余:第 12 章「变量注解 vs 值注解」会讲什么时候给变量标注反而要紧。
- 对象类型还有更多积木:交叉、继承、Record、索引签名全在第 07 章,本章只到「起名字」为止。
11. 可带走的¶
- 注解 = 名字后面
: 类型;推断 = 编译器看赋值自己猜。 - 函数参数必须注解;变量通常不用——参数没有赋值可供推断。
- 报错里的关键词是 assignable:「X is not assignable to Y」= X 放不进要 Y 的位置。
- 数字只有一个
number,没有 int/float 之分。 - 推断也管返回值;返回值注解日常可以省。
- any 关掉类型系统,连补全一起关——逃生门,不是客厅。
- 对象形状用
{}描述(对象字面量类型);?= 可选属性,读出来是T | undefined。 - 报错出现 missing / required,就是缺必填属性的信号。
type给类型起名字;起完名,那个定义就是单一事实源。import type导入类型;.types.ts是放共享类型的约定位置。
把前十条压成一句可以随身带的话:「参数标、变量猜、any 躲」—— 遇到任何新场景先按这三个词过一遍:这里有赋值可猜吗(变量)?没有就得标(参数)? 标不出来该换工具,而不是上 any(躲)。本章的全部机制,都是这三个词的细节展开。
12. 原文地图¶
| 主题 | 原书位置 | 原文位置 |
|---|---|---|
| 注解与冒号语法 | Type Annotations | text/10-fm-type-annotations.txt:3(搜「colon」) |
| 七种基本类型 | 同上 | text/10-fm-type-annotations.txt:9(搜「Hello World」) |
| 参数注解与调用检查 | 同上 | text/10-fm-type-annotations.txt:35(搜「Black Gold」) |
| 变量注解与重新赋值检查 | 同上 | text/10-fm-type-annotations.txt:51(搜「remains compatible」) |
| 推断:删掉注解也没错 | 同上 | text/10-fm-type-annotations.txt:92(搜「infer the type of each」) |
| 「基本不用给变量注解」 | 同上 | text/10-fm-type-annotations.txt:103(搜「mostly not annotate variables」) |
| 参数为什么不能推断 | 同上 | text/10-fm-type-annotations.txt:117(搜「can’t tell from a function parameter」) |
.map 回调的例外 |
同上 | text/10-fm-type-annotations.txt:129(搜「mapping over an array of strings」) |
| 推断返回值 | 同上 | text/10-fm-type-annotations.txt:203(搜「return types of functions」) |
| 没有 int/float,只有 number | 同上 | text/10-fm-type-annotations.txt:189(搜「int or float」) |
| any 的定义与三宗「任意」 | 同上 | text/10-fm-type-annotations.txt:143(搜「disables TypeScript」) |
| any 作逃生门及过度使用的警告 | 同上 | text/10-fm-type-annotations.txt:153(搜「escape hatch」) |
| any 关掉补全、拼写错误漏网 | 同上 | text/10-fm-type-annotations.txt:342(搜「disables useful features like autocompletion」) |
| 对象字面量类型的定义 | Object Literal Types | text/11-fm-object-literal-types.txt:9(搜「object literal type」) |
| 对象类型的补全回报 | Optional Object Properties | text/12-fm-optional-object-properties.txt:11(搜「autocomplete drop-down」) |
可选属性 ? |
同上 | text/12-fm-optional-object-properties.txt:3(搜「mark the age property as optional」) |
| missing / required 报错 | 同上 | text/12-fm-optional-object-properties.txt:90(搜「missing in type」) |
可选属性悬停为 string | undefined |
同上 | text/12-fm-optional-object-properties.txt:112(搜「string |
| type 别名与并集别名 | Type Aliases | text/13-fm-type-aliases.txt:30(搜「type Id」) |
| 类型别名 = 单一事实源 | 同上 | text/13-fm-type-aliases.txt:33(搜「single source of truth」) |
| export type / import type | Sharing Types Across Modules | text/14-fm-sharing-types-across-modules.txt:13(搜「import type」) |
.types.ts 约定 |
同上 | text/14-fm-sharing-types-across-modules.txt:15(搜「.types.ts」) |
| Rectangle 抽取练习 | 同上 | text/14-fm-sharing-types-across-modules.txt:66(搜「type Rectangle」) |
| 练习 4-1:只有 number 没有 int/float | Type Annotations | text/10-fm-type-annotations.txt:189(搜「int or float」) |
| 练习 4-2:参数注解无例外可讲 | 同上 | text/10-fm-type-annotations.txt:233(搜「(a: string, b: string)」) |
| 练习 4-3:删掉注解错误全消 | 同上 | text/10-fm-type-annotations.txt:278(搜「remove the explicit type annotations」) |
| 练习 4-4:e.terget 笔误现场 | 同上 | text/10-fm-type-annotations.txt:342(搜「disables useful features like autocompletion」) · text/10-fm-type-annotations.txt:347(搜「Whoops」) |
-
出处:「Type Annotations」第 3 段(
text/10-fm-type-annotations.txt:3,搜「colon」)。原文说明注解「告诉 TypeScript 某物应该是什么类型」,常用冒号。 ↩ -
出处:「Type Annotations」第 272 段(
text/10-fm-type-annotations.txt:272,搜「ensure that property keys are unique」)。symbol 类型用于 symbol 值,「确保属性键唯一」。 ↩ -
出处:「Type Annotations」第 276 段(
text/10-fm-type-annotations.txt:276,搜「MAX_SAFE_INTEGER」)。原文:大于Number.MAX_SAFE_INTEGER的数不能可靠相加,bigint 解决这个问题。 ↩ -
出处:「Type Annotations」第 35–39 段(
text/10-fm-type-annotations.txt:35,搜「Black Gold」)。 ↩ -
出处:「Type Annotations」第 51 段(
text/10-fm-type-annotations.txt:51,搜「remains compatible」)与第 67 段(搜「not assignable to type 'boolean'」)。 ↩ -
出处:「Type Annotations」第 86–92 段(
text/10-fm-type-annotations.txt:86,搜「hovering over each variable」)。 ↩ -
出处:「Type Annotations」第 94–101 段(
text/10-fm-type-annotations.txt:101,搜「toUpperCase」)。 ↩ -
出处:「Type Annotations」第 103 段(
text/10-fm-type-annotations.txt:103,搜「mostly not annotate variables」)。 ↩ -
出处:「Type Annotations」第 117 段(
text/10-fm-type-annotations.txt:117,搜「can’t tell from a function parameter」)。 ↩↩ -
出处:「Type Annotations」第 129–134 段(
text/10-fm-type-annotations.txt:129,搜「mapping over an array of strings」)。 ↩↩ -
出处:「Type Annotations」第 200–203 段(
text/10-fm-type-annotations.txt:203,搜「return types of functions」)。 ↩ -
出处:「Type Annotations」第 189 段(
text/10-fm-type-annotations.txt:189,搜「int or float」)。 ↩ -
出处:「Type Annotations」第 143 段(
text/10-fm-type-annotations.txt:143,搜「disables TypeScript’s type system」)与第 151 段(搜「error at runtime」)。 ↩↩ -
出处:「Type Annotations」第 153 段(
text/10-fm-type-annotations.txt:153,搜「escape hatch」)。 ↩ -
出处:「Type Annotations」第 342 段(
text/10-fm-type-annotations.txt:342,搜「disables useful features like autocompletion」)。拼写错误e.terget的实锤见第 347 段(搜「Whoops」)。 ↩↩ -
出处:「Object Literal Types」第 9 段(
text/11-fm-object-literal-types.txt:9,搜「object literal type」)。 ↩ -
出处:「Optional Object Properties」第 11 段(
text/12-fm-optional-object-properties.txt:11,搜「autocomplete drop-down」)。 ↩ -
出处:「Optional Object Properties」第 3–9 段(
text/12-fm-optional-object-properties.txt:5,搜「age?: number」)。 ↩ -
出处:「Optional Object Properties」第 109–112 段(
text/12-fm-optional-object-properties.txt:112,搜「string | undefined」)。 ↩ -
出处:「Optional Object Properties」第 88–91 段(
text/12-fm-optional-object-properties.txt:90,搜「missing in type」)。 ↩ -
出处:「Type Aliases」第 3–25 段(
text/13-fm-type-aliases.txt:5,搜「type keyword」)。 ↩ -
出处:「Type Aliases」第 30 段(
text/13-fm-type-aliases.txt:30,搜「type Id」)。 ↩ -
出处:「Type Aliases」第 33 段(
text/13-fm-type-aliases.txt:33,搜「single source of truth」)。 ↩ -
出处:「Sharing Types Across Modules」第 64–81 段(
text/14-fm-sharing-types-across-modules.txt:66,搜「type Rectangle」);「单一事实源」的措辞另见第 81 段(搜「single source of truth」)。 ↩↩ -
出处:「Sharing Types Across Modules」第 13 段(
text/14-fm-sharing-types-across-modules.txt:13,搜「import type」)。 ↩ -
出处:「Sharing Types Across Modules」第 15 段(
text/14-fm-sharing-types-across-modules.txt:15,搜「.types.ts」)。 ↩ -
出处:「Type Annotations」第 170 段(
text/10-fm-type-annotations.txt:170,搜「Expect」)。 ↩ -
出处:「Type Annotations」第 4-1 练习与解答(
text/10-fm-type-annotations.txt:189,搜「int or float」)。「TypeScript has only the number type」同段;返回值推断见第 202–203 段(搜「infer not only variables」)。 ↩↩ -
出处:「Type Annotations」练习 4-2 解答(
text/10-fm-type-annotations.txt:233,搜「concatTwoStrings = (a: string, b: string)」)。 ↩ -
出处:「Type Annotations」练习 4-3(
text/10-fm-type-annotations.txt:254,搜「not assignable to type 'string'」);五个正确答案见第 266–270 段。 ↩ -
出处:「Type Annotations」第 278–286 段(
text/10-fm-type-annotations.txt:278,搜「remove the explicit type annotations」)。「These basic types are useful to know, even if you don't always need them」见第 286 段。 ↩ -
出处:「Type Annotations」练习 4-4(
text/10-fm-type-annotations.txt:326,搜「originated in」)与测试报错文本;谜面「We've hidden a nasty typo in there」见第 336 段。 ↩ -
出处:「Type Annotations」练习 4-4 解答(
text/10-fm-type-annotations.txt:342,搜「disables useful features like autocompletion」)。「e.terget! Whoops!」见第 347 段;「We'll come back to this example in the future」见第 354 段。 ↩↩