关于 create-objects-all-at-once
这项 TypeScript 技能指导开发者一次性构造完整对象,而非逐步添加属性。它介绍对象字面量和展开语法,以保留类型检查并避免依赖类型断言。
主要功能
- 01一次创建包含全部属性的对象
- 02使用展开语法合并对象
- 03安全地添加条件属性
- 04用函数式结构转换数据
- 05避免不安全的类型断言
适用场景
- 01构建带类型的配置对象
- 02合并多个来源的属性
- 03按条件添加可选字段
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill create-objects-all-at-once安装后在 Claude Code 中输入/create-objects-all-at-once确认已加载。
详细介绍
一次性创建对象
概述
用一条语句构建对象,而不是逐步添加。
TypeScript 会在创建对象时推断其类型。之后再添加属性会导致类型错误。使用对象字面量和展开语法,一次性构建完整对象。
何时使用此技巧
- 逐步创建对象
- 创建对象后再添加属性
- 遇到“属性不存在”错误
- 合并多个对象
铁律
Define all properties in a single object literal.
Use spread syntax (...) to combine objects.
请记住:
- 对象类型在创建时就已固定
- 类型断言不是解决办法
- 对象展开可以保留类型安全
- 每次展开都会创建一个新变量
识别问题:增量构建会失败
const pt = {};
pt.x = 3;
// ~ Property 'x' does not exist on type '{}'
pt.y = 4;
// ~ Property 'y' does not exist on type '{}'
TypeScript 会将 pt 推断为 {},因此你无法向它添加属性。
类型断言“修复方案”(有问题)
interface Point { x: number; y: number; }
const pt = {} as Point; // Assertion silences errors
pt.x = 3;
pt.y = 4;
// But: TypeScript won't check you assigned all properties!
const pt2 = {} as Point; // No error, but x and y are undefined
类型断言会绕过安全检查。
解决方案:一次性构建完整对象
interface Point { x: number; y: number; }
const pt: Point = {
x: 3,
y: 4,
};
// TypeScript verifies all required properties are present
使用展开语法合并对象
不要使用 Object.assign:
const pt = { x: 3, y: 4 };
const id = { name: 'Origin' };
const namedPoint = {};
Object.assign(namedPoint, pt, id);
namedPoint.name;
// ~~~~~ Property 'name' does not exist on type '{}'
改用展开语法:
const pt = { x: 3, y: 4 };
const id = { name: 'Origin' };
const namedPoint = { ...pt, ...id };
// ^? { name: string; x: number; y: number }
namedPoint.name; // OK
安全地逐步构建对象
每一步都使用一个新变量:
const pt0 = {};
const pt1 = { ...pt0, x: 3 };
const pt: Point = { ...pt1, y: 4 }; // OK
// Each variable has a new, complete type
条件属性
使用展开语法有条件地添加属性:
declare let hasMiddle: boolean;
const firstLast = { first: 'Harry', last: 'Truman' };
const president = {
...firstLast,
...(hasMiddle ? { middle: 'S' } : {}),
};
// ^? { middle?: string; first: string; last: string }
条件属性在结果类型中会变成可选属性。
多个条件属性
declare let hasDates: boolean;
const nameTitle = { name: 'Khufu', title: 'Pharaoh' };
const pharaoh = {
...nameTitle,
...(hasDates && { start: -2589, end: -2566 }),
};
// ^? { start?: number; end?: number; name: string; title: string }
由于 start 和 end 是一起有条件地添加的,因此两者都是可选属性。
转换对象
转换数据时,使用函数式结构:
// Don't build incrementally
const result: Record<string, number> = {};
for (const item of items) {
result[item.name] = item.value; // Works but less type-safe
}
// Do use Array methods
const result = Object.fromEntries(
items.map(item => [item.name, item.value])
);
实际示例:配置对象
interface Config {
host: string;
port: number;
ssl?: boolean;
}
// Bad: incremental
const config = {} as Config;
config.host = 'localhost'; // Might forget port!
// Good: all at once
const config: Config = {
host: 'localhost',
port: 8080,
};
// Good: with conditional
const config: Config = {
host: 'localhost',
port: 8080,
...(useSSL && { ssl: true }),
};
抗压原则
1. “我需要动态构建对象”
压力:“属性来自不同的数据源”
**回应:**先收集所有数据,再构建对象。
**行动:**使用展开语法进行合并:{ ...source1, ...source2 }
2. “类型断言有效”
压力:“as Type 可以修复错误”
**回应:**它会绕过类型检查,因此不会检查缺失的属性。
**行动:**构建完整对象,让 TypeScript 对其进行验证。
危险信号——停下并重新考虑
const obj = {}后面跟着属性赋值- 使用
Object.assign构建对象 - 使用类型断言(
as)来消除“属性不存在”错误 - 在循环中向对象添加属性
常见借口(都不成立)
| 借口 | 事实 |
|---|---|
| “我还不知道所有属性” | 先收集数据,再构建对象 |
| “逐步构建更易读” | 对象字面量清晰且类型安全 |
| “类型断言可以修复问题” | 类型断言会绕过类型检查 |
快速参考
// DON'T: Build incrementally
const obj = {};
obj.a = 1; // Error
// DON'T: Use type assertion
const obj = {} as MyType;
// DO: Build all at once
const obj: MyType = { a: 1, b: 2 };
// DO: Combine with spread
const obj = { ...base, ...extra };
// DO: Conditional properties
const obj = { ...base, ...(cond && { opt: val }) };
总结
用一条语句完整地构建对象。
TypeScript 类型在对象创建时就已固定。使用对象字面量一次性定义所有属性。使用展开语法合并对象并添加条件属性。避免使用类型断言。
参考资料
改编自 Dan Vanderkam 的《Effective TypeScript》,条目 21:一次性创建对象。
评论
支持 Markdown