boolean 与字面量类型
摘要
总结 TypeScript 的 boolean、真假值转换及字面量类型,涵盖类型推断、拓宽、联合类型、as const 和判别联合。
1. boolean 基础
boolean 是 JavaScript 和 TypeScript 中表示逻辑状态的原始类型,只有两个可能值:true 和 false。
ts
let enabled: boolean = true;
enabled = false;
常见使用场景包括:
- 表示功能是否启用。
- 表示用户是否登录。
- 表示请求是否完成。
- 表示条件判断的结果。
- 控制分支、循环和界面状态。
1.1 类型推断
有明确初始值时,TypeScript 通常可以自动推断类型:
ts
let enabled = true;
enabled = false;
这里的 enabled 被推断为 boolean,通常不需要重复写类型注解。
以下场景更适合显式声明类型:
- 变量声明时没有初始值。
- 希望限制变量只能接收特定字面量或联合类型。
- 公共 API、对象模型或复杂返回值需要清楚表达契约。
ts
let isReady: boolean;
isReady = true;
1.2 比较表达式返回 boolean
ts
const age = 18;
const isAdult = age >= 18;
const isTeenager = age >= 13 && age < 18;
console.log(isAdult); // 输出: true
console.log(isTeenager); // 输出: false
===、!==、>、<、>=、<= 等比较运算符通常会产生布尔值。
1.3 逻辑运算符不一定返回 boolean
逻辑非 ! 一定返回布尔值,但 && 和 || 返回的是参与运算的某个原始操作数,不一定是 boolean。
ts
const username = '小明';
const displayName = username || '匿名用户';
const result = username && 100;
console.log(displayName); // 输出: 小明
console.log(result); // 输出: 100
console.log(!username); // 输出: false
如果业务需要真正的布尔值,应使用 Boolean(value) 或 !!value 明确转换。
官方文档 :TypeScript Handbook:The primitives string、number and boolean
2. boolean 与 Boolean 的区别
2.1 小写 boolean
boolean 表示原始布尔值,是类型注解中应该使用的类型:
ts
const enabled: boolean = true;
2.2 大写 Boolean
Boolean 是 JavaScript 的包装对象构造函数及相应对象类型。使用 new Boolean() 会创建对象,而不是原始布尔值:
ts
const wrapped = new Boolean(false);
console.log(typeof wrapped); // 输出: object
console.log(wrapped.valueOf()); // 输出: false
包装对象本身是对象,所以即使它包装的是 false,放在条件中仍然属于真值:
ts
const wrapped = new Boolean(false);
if (wrapped) {
console.log('条件成立'); // 输出: 条件成立
}
因此,不要使用 new Boolean() 保存业务布尔状态,也不要在类型注解中使用大写 Boolean。
ts
let enabled: boolean = true;
// enabled = new Boolean(false);
// 错误:Boolean 对象不能赋给 boolean 原始类型
2.3 Boolean(value) 是布尔转换
不使用 new 时,Boolean(value) 会返回原始的 boolean:
ts
const enabled = Boolean(1);
console.log(enabled); // 输出: true
console.log(typeof enabled); // 输出: boolean
官方文档 :TypeScript Declaration Files:不要使用包装对象类型
3. 真值与假值
JavaScript 在条件判断中会把值转换为布尔值。这个规则称为 truthiness。
3.1 常见假值
以下值转换为布尔值后是 false:
ts
console.log(Boolean(false)); // 输出: false
console.log(Boolean(0)); // 输出: false
console.log(Boolean(-0)); // 输出: false
console.log(Boolean(0n)); // 输出: false
console.log(Boolean('')); // 输出: false
console.log(Boolean(null)); // 输出: false
console.log(Boolean(undefined)); // 输出: false
console.log(Boolean(NaN)); // 输出: false
0n 需要支持 BigInt 的运行环境和编译目标。
3.2 常见真值
除少数假值外,大多数值都会转换为 true。特别注意,空数组和空对象也是真值:
ts
console.log(Boolean('false')); // 输出: true
console.log(Boolean('0')); // 输出: true
console.log(Boolean([])); // 输出: true
console.log(Boolean({})); // 输出: true
字符串内容看起来像 false 或 0,不代表它会转换为布尔值 false。只要字符串非空,它通常就是真值。
3.3 Boolean() 与 !!
两种写法都能进行布尔转换:
ts
const value: string | undefined = 'hello';
const convertedByFunction = Boolean(value);
const convertedByNegation = !!value;
通常:
Boolean(value)可读性更直接,推断结果一般是boolean。!!value使用两次逻辑非完成转换;当表达式本身是编译器已知的固定真值或假值时,可能保留true或false字面量类型。
ts
const greeting = 'hello' as const;
const convertedByFunction = Boolean(greeting);
const convertedByNegation = !!greeting;
可以把推断结果理解为:
ts
type ByFunction = boolean;
type ByNegation = true;
3.4 类型断言不会执行布尔转换
ts
const value = 'false';
// const enabled = value as boolean;
// 错误思路:类型断言不会把字符串转换成布尔值
需要运行时转换时,应该真正调用 Boolean(value) 或使用 !!value。类型断言只影响编译阶段,不会改变实际数据。
4. Truthiness narrowing
TypeScript 会根据条件判断进行控制流分析,排除不可能的类型。
ts
let value: string | null = Math.random() > 0.5 ? 'hello' : null;
if (value) {
value.toUpperCase();
}
进入 if 分支后,TypeScript 知道 value 不再是 null;对于字符串,还意味着当前值不是空字符串。
4.1 Truthiness 检查可能合并多个业务状态
ts
let count: number | undefined = 0;
if (count) {
console.log('存在非零数量');
}
这里的 0 会和 undefined 一样进入假值分支。如果 0 是合法业务数据,就不应该只使用 truthiness 判断。
更精确的写法是显式检查:
ts
if (count !== undefined) {
console.log(count); // 输出: 0
}
4.2 显式比较布尔值
普通布尔变量通常直接判断即可:
ts
const isReady = true;
if (isReady) {
console.log('准备完成'); // 输出: 准备完成
}
当值不仅包含 boolean,或者需要明确区分 false、undefined 等状态时,可以使用严格比较:
ts
let enabled: boolean | undefined = false;
if (enabled === false) {
console.log('功能已明确关闭'); // 输出: 功能已明确关闭
}
官方文档 :TypeScript Handbook:Truthiness narrowing
5. 什么是字面量类型
字面量是直接写在代码中的具体值,例如 'success'、200、true。当具体值出现在类型位置时,就形成字面量类型。
ts
let status: 'success' = 'success';
let code: 200 = 200;
let enabled: true = true;
字面量类型只允许一个确定的值:
ts
let status: 'success' = 'success';
// status = 'failed';
// 错误:'failed' 不能赋给 'success'
可以把它理解为更具体的基础类型:
text
'success' ⊂ string
200 ⊂ number
true ⊂ boolean
6. 常见字面量类型
6.1 字符串字面量类型
ts
type Direction = 'left' | 'right';
let direction: Direction = 'left';
direction = 'right';
6.2 数字字面量类型
ts
type HttpStatus = 200 | 400 | 404 | 500;
let status: HttpStatus = 200;
status = 404;
6.3 布尔字面量类型
ts
let enabled: true = true;
// enabled = false;
// 错误:false 不能赋给 true
boolean 可以理解为 true | false 的联合:
ts
type MyBoolean = true | false;
由于 true | false 与 boolean 表达的值域相同,普通布尔状态直接使用 boolean 即可。单独使用 true 或 false 的价值在于表达更精确的对象分支或函数契约。
6.4 BigInt 字面量类型
ts
type BinaryDigit = 0n | 1n;
let digit: BinaryDigit = 0n;
digit = 1n;
BigInt 字面量需要相应的 target、lib 和运行环境支持。
7. 字面量联合类型
单个字面量类型通常过于严格。实际开发更常见的做法是把多个允许值组合成联合类型。
ts
type Theme = 'light' | 'dark' | 'system';
let theme: Theme = 'light';
theme = 'dark';
// theme = 'blue';
// 错误:'blue' 不属于 Theme
7.1 适合表示有限状态
ts
type RequestStatus = 'idle' | 'loading' | 'success' | 'error';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Alignment = 'left' | 'center' | 'right';
字面量联合类型能够同时提供:
- 编译阶段的合法值检查。
- 编辑器自动补全。
- 重构时的引用提示。
- 比普通
string更清晰的业务语义。
7.2 与枚举的区别
字符串字面量联合不会生成额外的 JavaScript 对象,适合只需要类型约束的场景。枚举具有自己的运行时表现和成员访问方式,是否使用应根据项目规范和运行时需求决定。
7.3 与普通联合类型组合
ts
type Id = number | `user_${number}`;
let id: Id = 1001;
id = 'user_1001';
8. 模板字面量类型
模板字面量类型可以在类型层面组合字符串字面量,生成具有固定格式的字符串类型。
ts
type EventName = 'click' | 'focus';
type HandlerName = `on${Capitalize<EventName>}`;
HandlerName 等价于:
ts
type HandlerName = 'onClick' | 'onFocus';
也可以约束字符串格式:
ts
type UserId = `user_${number}`;
const id: UserId = 'user_1001';
// const invalidId: UserId = 'order_1001';
// 错误:字符串格式不符合 UserId
模板字面量类型只在编译阶段约束字符串格式,不会在运行时验证外部输入。
9. 字面量类型的推断与拓宽
9.1 let 通常拓宽为基础类型
ts
let status = 'success';
let code = 200;
let enabled = true;
由于变量允许重新赋值,推断结果通常是:
ts
type Status = string;
type Code = number;
type Enabled = boolean;
9.2 基本类型的 const 通常保留字面量类型
ts
const status = 'success';
const code = 200;
const enabled = true;
推断结果通常是:
ts
type Status = 'success';
type Code = 200;
type Enabled = true;
9.3 const 对象的可写属性仍会拓宽
ts
const config = {
theme: 'dark',
enabled: true,
};
const 只禁止 config 变量重新赋值,不禁止属性修改,因此通常推断为:
ts
type Config = {
theme: string;
enabled: boolean;
};
以下修改是允许的:
ts
config.theme = 'light';
config.enabled = false;
9.4 显式类型注解控制值域
ts
type Theme = 'light' | 'dark';
const config: { theme: Theme; enabled: boolean } = {
theme: 'dark',
enabled: true,
};
config.theme 可以修改为 'light' 或 'dark',但不能变成任意字符串。
9.5 as const 保留字面量并添加只读约束
ts
const config = {
theme: 'dark',
enabled: true,
} as const;
推断结果可以理解为:
ts
type Config = {
readonly theme: 'dark';
readonly enabled: true;
};
as const 只影响 TypeScript 的推断,不会在运行时冻结对象。
9.6 satisfies 校验结构并保留自身推断
ts
type ConfigShape = {
theme: 'light' | 'dark';
enabled: boolean;
};
const config = {
theme: 'dark',
enabled: true,
} satisfies ConfigShape;
satisfies 的主要作用是检查表达式是否满足目标结构,同时避免直接把变量整体标注为目标类型。具体属性是否保留为字面量类型,仍会受到目标类型和上下文推断影响。
官方文档 :TypeScript 2.1:Better inference for literal types
10. 使用布尔字面量构建判别联合
布尔字面量适合充当对象联合类型的判别字段,让不同状态携带不同数据。
ts
type SuccessResult<T> = {
success: true;
data: T;
};
type FailureResult = {
success: false;
error: string;
};
type Result<T> = SuccessResult<T> | FailureResult;
使用判别字段后,TypeScript 可以自动收窄分支:
ts
type User = {
id: number;
name: string;
};
declare const result: Result<User>;
if (result.success) {
result.data.name;
} else {
result.error;
}
相比下面这种结构,判别联合能够建立状态与数据之间的对应关系:
ts
type LooseResult<T> = {
success: boolean;
data?: T;
error?: string;
};
LooseResult<T> 无法仅凭 success 保证 data 或 error 一定存在,而判别联合可以。
11. 布尔值与多状态建模
布尔值只适合表示两个互斥状态。当业务实际存在三个或更多状态时,不要强行使用多个布尔变量拼接状态。
容易产生无效组合的写法:
ts
type RequestState = {
isLoading: boolean;
isSuccess: boolean;
isError: boolean;
};
该类型允许三个字段同时为 true,但业务上可能不合理。
更清晰的写法是使用字面量联合:
ts
type RequestStatus = 'idle' | 'loading' | 'success' | 'error';
type RequestState = {
status: RequestStatus;
};
如果不同状态还携带不同数据,应进一步使用判别联合。
12. 常见误区
误区 1:'false' 会转换为 false
'false' 是非空字符串,转换结果是 true。解析表单、URL 或存储中的文本布尔值时,应显式比较字符串内容。
ts
const value = 'false';
const enabled = value === 'true';
console.log(enabled); // 输出: false
误区 2:new Boolean(false) 是假值
它是包装对象,对象本身是真值。业务代码应使用原始 boolean。
误区 3:&& 和 || 一定返回布尔值
它们返回操作数。需要布尔结果时,应明确转换或使用产生布尔值的比较表达式。
误区 4:truthiness 判断能区分所有业务状态
0、''、null、undefined 等都会进入假值分支。如果这些值含义不同,应使用严格比较或更明确的联合类型。
误区 5:const 对象属性会自动保留字面量类型
对象属性默认可写,因此通常会拓宽。需要只读字面量推断时,使用 as const;需要可写且值域受限时,使用显式联合类型。
误区 6:字面量类型越窄越好
过窄的类型会阻止正常修改。类型应表达真实业务约束,而不是一味追求最具体。
误区 7:类型约束会自动验证外部数据
TypeScript 类型在编译后会被移除。接口响应、表单值和本地存储数据仍需要运行时解析与校验。
13. 实际开发原则
- 类型注解使用小写
boolean,不要使用包装对象类型Boolean。 - 不使用
new Boolean()保存业务状态。 - 进行布尔转换时使用
Boolean(value)或!!value,不要使用类型断言代替转换。 0、''等值具有独立业务含义时,使用显式比较,不要只依赖 truthiness。- 只有两个互斥状态时使用
boolean;存在多个状态时使用字面量联合。 - 有限字符串、数字或布尔值域使用字面量联合,避免退化为宽泛的
string、number。 - 需要建立状态与数据的对应关系时,使用判别联合。
- 需要保留对象字面量的精确类型时,根据可变性选择显式注解、
as const或satisfies。
14. 一句话总结
boolean表示true或false,字面量类型把类型约束到具体值;通过联合类型和判别字段,可以准确表达有限状态及其数据关系。