You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为逐步添加属性的对象进行TypeScript类型定义?

逐步为非可选属性对象赋值的类型安全方案

我经常遇到这种场景:需要逐步给对象添加属性,属性值要经过计算才能确定。举个实际代码的例子:

declare const getString: () => string;
declare const map: Map<string, unknown>

type Thing = {
  foo: string;
};

const thing: Thing = {} // 错误:类型“{}”中缺少类型“Thing”所需的属性“foo”。

const string = getString() // 假设此操作开销极大或仅能执行一次。
if (map.has(string)) thing.foo = string
else thing.foo = "blank"

实际场景里属性会更多,核心问题是:这些属性必须是非可选的,但我没法在创建对象时就直接赋值;同时还得保留类型约束,防止给对象赋无效值。

我的尝试与遇到的问题

首次尝试:Partial类型转换

一开始我用Partial创建对象,再尝试转换成目标类型:

// 函数返回类型
function createThing(): Thing {
  const thing: Partial<Thing> = {}
  // ...赋值逻辑
  return thing // 错误:Partial<Thing> 不能赋值给 Thing
}

// 变量赋值
const thing: Partial<Thing> = {}
// ...赋值逻辑
const thing2: Thing = thing // 同样报错

这种方式行不通,因为Partial<Thing>的类型不会自动收窄,TypeScript始终认为它可能缺少属性。

尝试动态添加属性的函数

后来我试过写一个动态添加属性的函数,希望能更新对象类型:

const addProp = <T extends object, K extends keyof Thing, V>(
  obj: T,
  key: K,
  value: V
): T & Record<K, V> => Object.assign(obj, { [key]: value } as Record<K, V>);

let thing: Partial<Thing> = {};
thing = addProp(thing, "foo", "blank");

函数返回的类型是对的,但变量thing的类型还是Partial<Thing>,每次赋值都得重新分配变量,很繁琐。

类型断言函数的局限

我也试过用类型断言函数,但这类函数要求必须指定所有泛型参数,使用起来不够灵活,没法自动推导。


可行的解决方案

方案1:手动断言+前置检查

如果属性数量不多,可以在确认所有属性都赋值完成后,用类型断言转换。关键是要手动保证所有必填属性都已设置:

declare const getString: () => string;
declare const map: Map<string, unknown>

type Thing = {
  foo: string;
};

const thing: Partial<Thing> = {};

const string = getString();
if (map.has(string)) thing.foo = string;
else thing.foo = "blank";

// 确认所有属性都已赋值后断言
const finalThing = thing as Thing;

方案2:自定义类型断言函数

写一个针对目标类型的断言函数,不用每次指定泛型,还能在运行时做校验:

type Thing = {
  foo: string;
};

function assertIsThing(obj: unknown): asserts obj is Thing {
  if (
    !obj || 
    typeof obj !== 'object' || 
    !('foo' in obj) || 
    typeof (obj as Thing).foo !== 'string'
  ) {
    throw new Error('对象不符合Thing类型要求');
  }
}

// 使用方式
const thing: Partial<Thing> = {};
const string = getString();
thing.foo = map.has(string) ? string : "blank";

assertIsThing(thing);
// 此时thing的类型已被收窄为Thing
console.log(thing.foo); // 类型为string,无报错

方案3:构建器模式(适合多属性场景)

如果对象有很多属性,可以用构建器类来逐步设置,最后返回完整类型的对象,同时在build方法里做必填属性校验:

type Thing = {
  foo: string;
  bar: number;
};

class ThingBuilder {
  private thing: Partial<Thing> = {};

  setFoo(value: string): this {
    this.thing.foo = value;
    return this;
  }

  setBar(value: number): this {
    this.thing.bar = value;
    return this;
  }

  build(): Thing {
    if (!this.thing.foo) throw new Error('foo是必填属性');
    if (this.thing.bar === undefined) throw new Error('bar是必填属性');
    return this.thing as Thing;
  }
}

// 使用方式
const string = getString();
const thing = new ThingBuilder()
  .setFoo(map.has(string) ? string : "blank")
  .setBar(123)
  .build();

// thing的类型是Thing,可直接使用

方案4:利用satisfies操作符(TypeScript 4.9+)

如果用的是TS 4.9及以上版本,可以用satisfies来验证中间状态的类型,最后再断言:

type Thing = {
  foo: string;
};

const thing = {} satisfies Partial<Thing>;

const string = getString();
thing.foo = map.has(string) ? string : "blank";

// 验证并转换类型
const finalThing = thing satisfies Thing as Thing;

内容的提问来源于stack exchange,提问作者Darryl Noakes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 19:54:54