mcpField
C
create-objects-all-at-once

create-objects-all-at-once

github.com/marius-townhouse开发643 次安装38 周前更新
代码仓库

关于 create-objects-all-at-once

这项 TypeScript 技能指导开发者一次性构造完整对象,而非逐步添加属性。它介绍对象字面量和展开语法,以保留类型检查并避免依赖类型断言。

主要功能

  1. 01一次创建包含全部属性的对象
  2. 02使用展开语法合并对象
  3. 03安全地添加条件属性
  4. 04用函数式结构转换数据
  5. 05避免不安全的类型断言

适用场景

  1. 01构建带类型的配置对象
  2. 02合并多个来源的属性
  3. 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