编程语言研究书库

注解入门 — 基本类型、对象字面量、可选属性与类型别名

注解入门 — 基本类型、对象字面量、可选属性与类型别名

这一章讲四件事: 冒号语法和第一批类型词汇;「什么时候要注解、什么时候让编译器自己猜」的分工规则; 怎么描述对象的形状(包括「某个属性可以有可以没有」);以及怎么给类型起名字、跨文件复用。 读完你会写出第一批真正的 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 三个机制,从头走一遍。

第一版:参数注解。

const logAlbumInfo = (
  title: string,
  trackCount: number,
  isReleased: boolean,
) => {
  // 实现
};

三个参数各带一个注解。注解的回报是调用检查:你传错顺序试试——

logAlbumInfo("Black Gold", false, 15); // 红线先画在 false,再画在 15

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。但现在把注解全删掉试试:

let albumTitle = "Midnights";
let isReleased = true;
let trackCount = 13;

一个错误都没有。悬停三个变量,类型端端正正地在那里: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。

let anyVariable: any = "这可以是任何东西!";

anyVariable();                    // 不报错
anyVariable.deep.property.access; // 不报错

第一行运行到就炸——但 TypeScript 一声不吭。13

any 不是魔鬼:类型复杂到描述不了时、把老 JavaScript 项目迁到 TypeScript 时, 它是可用的逃生门(escape hatch)14。但原书用练习演示了它的真实代价,值得转述: 一个表单处理函数把参数标成 any,代码里藏着一处拼写错误(e.terget,应为 e.target), 编辑器全程安静——因为 any 不但关掉检查,连自动补全也一起关了, 没有了补全,拼错属性这种最typical的错误就再也拦不住15。

判断(我们的,不是书里的): 这一章把 any 讲得比多数入门材料诚实—— 它没有只说「别用 any」,而是让你看见 any 的具体损失函数:检查 + 补全 + 重构,三样一起没。 实用的推论是:嫌类型麻烦而标 any 的地方,往往正是类型最能帮上忙的地方(边界数据、遗留代码)。 如果错,会错在: 有些场景(快速原型、一次性脚本)any 的损失确实接近零, 这条推论对那些场景是过度紧张。

5. 描述对象的形状:对象字面量类型

解决什么问题: 函数参数常常不是一个简单值,而是一个对象。怎么描述「这个对象应该长什么样」?

const talkToAnimal = (animal: {name: string; type: string; age: number}) => {
  // 函数体
};

参数后面那对大括号不是在创建一个对象——它是在描述一个形状: 这个对象要有 name(字符串)、type(字符串)、age(数字)三个属性。 这个语法有个正式名字:对象字面量类型(object literal type)16。

对象类型的回报立刻兑现:调用函数时,补全列表会直接给出三个属性名, 属性多、名字像的时候,这既省时间又防拼错17。

可选属性:?,以及它和 | undefined 的关系

解决什么问题: 不是每个属性都必填。把 age 标成可选的,在属性名后加一个问号:

const talkToAnimal = (animal: {name: string; type: string; age?: number}) => {};

调用时就可以不给 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):

type Animal = {
  name: string;
  type: string;
  age?: number;
};

从此 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(整数小数通吃)27:

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 不只推断变量,也推断函数返回类型27。

练习 4-2:参数注解是最没有商量余地的一类

concatTwoStrings(a, b) 两个裸参数,双双「implicitly has an 'any' type」。 解法没有任何花样:(a: string, b: string)28。 这道题的要点不在解法,而在它和其他练习题的对照:返回值可以永远不标(推断兜底), 参数却几乎必须标——同一道题里,出题人把两类注解的待遇差摆在了明面上。

练习 4-3:七种基本类型的连珠报错

五个变量全部错标成 string,悬停逐个给出 Type 'number' is not assignable to type 'string' 一类的报错, 改回各自的正确类型(string/number/boolean/symbol/bigint)即过29。 这道题真正的教学点在题外:原书紧接着把五个注解全删掉——一个错误都没了, 因为推断对每个值都给出了正确的类型30。 「基本类型值得认识,即使你并不总需要拿它们标注变量」——这就是第 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;
};

编译器全程无红线;测试一跑,炸31。谜底:e.terget 是个不存在的属性—— 如果 e 有正经类型,这一行立刻报错;正因为它是 any, TypeScript 连「这个属性不存在」都无权告诉你。 原书把损失清单说得具体:any 不止关掉类型检查,还关掉了自动补全—— 而补全本来是能当场拦住这个笔误的32。

  any 的完整损失清单(比「不报错」长):
    1. 类型报错消失        —— 错误流进运行时
    2. 自动补全消失        —— 拼写错误当场没人拦
    3. 悬停信息变成 any    —— 读代码的人失去地图
  图说:标 any 省掉的是注解的那几秒,欠下的是上面三样。练习 4-4 的
  笔误只是清单第 2 条的一次现场抓获[^35]。

这道题还埋了个后话:原书说「以后会回到这个例子,看正确的类型怎么写」32—— 这个伏笔在第 05 章(JSON.parse 永远返回 any)和第 12 章(satisfies) 都会兑现,any 的正确替代品不是「另一个 any」。

9. 作者的判断与证据(分开标)

书里有据的:

  • 「参数必须注解、变量不用」——由「变量有赋值可看、参数没有」这个机制解释支撑9,并给出了 .map 这一可验证的例外10。
  • 「any 关掉的不仅是检查还有补全」——用带拼写错误的练习代码实证:红线缺席、测试运行时才炸15。
  • 「类型别名 = 单一事实源」——工程原则主张,由「两个函数共享 Rectangle」的练习展示收益24。

原书没说的(我们补):

  • 「推断优先、注解兜底」与工业界其他静态语言(Java、C#)「处处显式」风格的对比—— 补充(不在书里,来自通用知识): 这是「渐进类型」语言共有的实用主义姿态; TypeScript 的编译器默认严格度(第 15 章)让你自己决定站到「显式」还是「简洁」那一端。
  • 练习里用到的 Expect/Equal 类型级断言是 totaltypescript 练习库的工具,原书一笔带过33—— 它们的原理(用类型层面做相等检查)属第 16 章的条件类型领域。

10. 边界与局限

  • 基本类型清单不完整:object、unknown、never 等基本类型本章没出场(unknown 第 06、13 章,never 第 06 章)。
  • any 只是初见:第 13 章会讲一个 any 的「变异体」evolving any,比这里的 any 微妙得多。
  • 变量注解并非永远多余:第 12 章「变量注解 vs 值注解」会讲什么时候给变量标注反而要紧。
  • 对象类型还有更多积木:交叉、继承、Record、索引签名全在第 07 章,本章只到「起名字」为止。

11. 可带走的

  1. 注解 = 名字后面 : 类型;推断 = 编译器看赋值自己猜。
  2. 函数参数必须注解;变量通常不用——参数没有赋值可供推断。
  3. 报错里的关键词是 assignable:「X is not assignable to Y」= X 放不进要 Y 的位置。
  4. 数字只有一个 number,没有 int/float 之分。
  5. 推断也管返回值;返回值注解日常可以省。
  6. any 关掉类型系统,连补全一起关——逃生门,不是客厅。
  7. 对象形状用 {} 描述(对象字面量类型);? = 可选属性,读出来是 T | undefined。
  8. 报错出现 missing / required,就是缺必填属性的信号。
  9. type 给类型起名字;起完名,那个定义就是单一事实源。
  10. import type 导入类型;.types.ts 是放共享类型的约定位置。

把前十条压成一句可以随身带的话:「参数标、变量猜、any 躲」—— 遇到任何新场景先按这三个词过一遍:这里有赋值可猜吗(变量)?没有就得标(参数)? 标不出来该换工具,而不是上 any(躲)。本章的全部机制,都是这三个词的细节展开。

12. 原文地图

主题原书位置原文位置
注解与冒号语法Type Annotationstext/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 Typestext/11-fm-object-literal-types.txt:9(搜「object literal type」)
对象类型的补全回报Optional Object Propertiestext/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」)
可选属性悬停为 `stringundefined`同上
type 别名与并集别名Type Aliasestext/13-fm-type-aliases.txt:30(搜「type Id」)
类型别名 = 单一事实源同上text/13-fm-type-aliases.txt:33(搜「single source of truth」)
export type / import typeSharing Types Across Modulestext/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/floatType Annotationstext/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」)

Footnotes

  1. 出处:「Type Annotations」第 3 段(text/10-fm-type-annotations.txt:3,搜「colon」)。原文说明注解「告诉 TypeScript 某物应该是什么类型」,常用冒号。 ↩

  2. 出处:「Type Annotations」第 272 段(text/10-fm-type-annotations.txt:272,搜「ensure that property keys are unique」)。symbol 类型用于 symbol 值,「确保属性键唯一」。 ↩

  3. 出处:「Type Annotations」第 276 段(text/10-fm-type-annotations.txt:276,搜「MAX_SAFE_INTEGER」)。原文:大于 Number.MAX_SAFE_INTEGER 的数不能可靠相加,bigint 解决这个问题。 ↩

  4. 出处:「Type Annotations」第 35–39 段(text/10-fm-type-annotations.txt:35,搜「Black Gold」)。 ↩

  5. 出处:「Type Annotations」第 51 段(text/10-fm-type-annotations.txt:51,搜「remains compatible」)与第 67 段(搜「not assignable to type 'boolean'」)。 ↩

  6. 出处:「Type Annotations」第 86–92 段(text/10-fm-type-annotations.txt:86,搜「hovering over each variable」)。 ↩

  7. 出处:「Type Annotations」第 94–101 段(text/10-fm-type-annotations.txt:101,搜「toUpperCase」)。 ↩

  8. 出处:「Type Annotations」第 103 段(text/10-fm-type-annotations.txt:103,搜「mostly not annotate variables」)。 ↩

  9. 出处:「Type Annotations」第 117 段(text/10-fm-type-annotations.txt:117,搜「can’t tell from a function parameter」)。 ↩ ↩2

  10. 出处:「Type Annotations」第 129–134 段(text/10-fm-type-annotations.txt:129,搜「mapping over an array of strings」)。 ↩ ↩2

  11. 出处:「Type Annotations」第 200–203 段(text/10-fm-type-annotations.txt:203,搜「return types of functions」)。 ↩

  12. 出处:「Type Annotations」第 189 段(text/10-fm-type-annotations.txt:189,搜「int or float」)。 ↩

  13. 出处:「Type Annotations」第 143 段(text/10-fm-type-annotations.txt:143,搜「disables TypeScript’s type system」)与第 151 段(搜「error at runtime」)。 ↩ ↩2

  14. 出处:「Type Annotations」第 153 段(text/10-fm-type-annotations.txt:153,搜「escape hatch」)。 ↩

  15. 出处:「Type Annotations」第 342 段(text/10-fm-type-annotations.txt:342,搜「disables useful features like autocompletion」)。拼写错误 e.terget 的实锤见第 347 段(搜「Whoops」)。 ↩ ↩2

  16. 出处:「Object Literal Types」第 9 段(text/11-fm-object-literal-types.txt:9,搜「object literal type」)。 ↩

  17. 出处:「Optional Object Properties」第 11 段(text/12-fm-optional-object-properties.txt:11,搜「autocomplete drop-down」)。 ↩

  18. 出处:「Optional Object Properties」第 3–9 段(text/12-fm-optional-object-properties.txt:5,搜「age?: number」)。 ↩

  19. 出处:「Optional Object Properties」第 109–112 段(text/12-fm-optional-object-properties.txt:112,搜「string | undefined」)。 ↩

  20. 出处:「Optional Object Properties」第 88–91 段(text/12-fm-optional-object-properties.txt:90,搜「missing in type」)。 ↩

  21. 出处:「Type Aliases」第 3–25 段(text/13-fm-type-aliases.txt:5,搜「type keyword」)。 ↩

  22. 出处:「Type Aliases」第 30 段(text/13-fm-type-aliases.txt:30,搜「type Id」)。 ↩

  23. 出处:「Type Aliases」第 33 段(text/13-fm-type-aliases.txt:33,搜「single source of truth」)。 ↩

  24. 出处:「Sharing Types Across Modules」第 64–81 段(text/14-fm-sharing-types-across-modules.txt:66,搜「type Rectangle」);「单一事实源」的措辞另见第 81 段(搜「single source of truth」)。 ↩ ↩2

  25. 出处:「Sharing Types Across Modules」第 13 段(text/14-fm-sharing-types-across-modules.txt:13,搜「import type」)。 ↩

  26. 出处:「Sharing Types Across Modules」第 15 段(text/14-fm-sharing-types-across-modules.txt:15,搜「.types.ts」)。 ↩

  27. 出处:「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」)。 ↩ ↩2

  28. 出处:「Type Annotations」练习 4-2 解答(text/10-fm-type-annotations.txt:233,搜「concatTwoStrings = (a: string, b: string)」)。 ↩

  29. 出处:「Type Annotations」练习 4-3(text/10-fm-type-annotations.txt:254,搜「not assignable to type 'string'」);五个正确答案见第 266–270 段。 ↩

  30. 出处:「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 段。 ↩

  31. 出处:「Type Annotations」练习 4-4(text/10-fm-type-annotations.txt:326,搜「originated in」)与测试报错文本;谜面「We've hidden a nasty typo in there」见第 336 段。 ↩

  32. 出处:「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 段。 ↩ ↩2

  33. 出处:「Type Annotations」第 170 段(text/10-fm-type-annotations.txt:170,搜「Expect」)。 ↩

On this page