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

如何在TypeScript中动态设置作用域链上的属性?

在TypeScript中实现类型安全的嵌套作用域属性设置

问题背景

我有如下嵌套结构的作用域树:

const parentScope = {
  data: {
    foo: 'bar'
  }
}
const child1Scope = {
  parent: parentScope,
  data: {
    hello: 'world',
    one: 2
  }
}
const child2Scope = {
  parent: child1Scope,
  data: {
    a: true
  }
}

希望通过以下方式使用:

setProperty(child2Scope, 'a', false)
setProperty(child2Scope, 'hello', 'there')
setProperty(child2Scope, 'foo', 'baz')

function setProperty(scope, property, value) {
  if (property in scope.data) {
    scope.data[property] = value
  } else if (scope.parent) {
    setProperty(scope.parent, property, value)
  } else {
    throw new Error(`Property not defined on scope`)
  }
}

需要在TypeScript中正确实现该功能,每个作用域已有预定义类型结构。我的作用域类型定义尝试如下(parent可能为unknown):

export type ScopeType<
  A extends Scope,
  B extends ScopeType<Scope> | unknown = unknown,
> = {
  data: ScopeTableType[A]
  like: A
  parent?: B
}

给setProperty添加类型标注的尝试:

function setProperty(scope: ScopeType<Scope>, property: keyof ScopeType<Scope>, value: unknown) {
  if (property in scope.data) {
    scope.data[property] = value
  } else if (scope.parent) {
    setProperty(scope.parent, property, value)
  } else {
    throw new Error(`Property not defined on scope`)
  }
}

正确实现方案

1. 定义基础类型映射

先明确所有作用域的标识和对应的data结构:

// 作用域标识类型
type Scope = 'parent' | 'child1' | 'child2';

// 作用域data结构映射表
type ScopeDataMap = {
  parent: { foo: string };
  child1: { hello: string; one: number };
  child2: { a: boolean };
};

2. 递归定义作用域类型

修正ScopeType,让它能准确表示嵌套的父作用域类型:

// T: 当前作用域标识,Parent: 父作用域类型(可选)
type ScopeType<
  T extends Scope,
  Parent extends ScopeType<any> | undefined = undefined
> = {
  data: ScopeDataMap[T];
  like: T;
  parent?: Parent;
};

3. 标注作用域实例

给每个作用域实例明确类型,绑定正确的父作用域:

const parentScope: ScopeType<'parent'> = {
  data: { foo: 'bar' },
  like: 'parent'
};

const child1Scope: ScopeType<'child1', typeof parentScope> = {
  parent: parentScope,
  data: { hello: 'world', one: 2 },
  like: 'child1'
};

const child2Scope: ScopeType<'child2', typeof child1Scope> = {
  parent: child1Scope,
  data: { a: true },
  like: 'child2'
};

4. 实现类型安全的setProperty

通过辅助类型提取作用域链的所有属性,再实现带类型校验的函数:

// 递归提取作用域链中所有data的属性类型
type ScopeChainProperties<S> = 
  S extends ScopeType<any, infer P> 
    ? ScopeDataMap[S['like']] & (P extends undefined ? {} : ScopeChainProperties<P>)
    : {};

// 提取作用域链中所有属性名
type AllScopeProperties<S> = keyof ScopeChainProperties<S>;

function setProperty<
  S extends ScopeType<any, any>
>(
  scope: S,
  property: AllScopeProperties<S>,
  value: ScopeChainProperties[S][typeof property]
): void {
  if (property in scope.data) {
    // 运行时已校验属性存在,类型断言安全
    (scope.data as Record<string, unknown>)[property] = value;
  } else if (scope.parent) {
    // 递归调用父作用域,自动继承类型约束
    setProperty(scope.parent, property, value);
  } else {
    throw new Error(`Property not defined on scope`);
  }
}

5. 验证类型安全

调用时TypeScript会自动校验属性存在性和值类型:

// 合法调用
setProperty(child2Scope, 'a', false);
setProperty(child2Scope, 'hello', 'there');
setProperty(child2Scope, 'foo', 'baz');

// 以下调用会触发编译错误
// setProperty(child2Scope, 'nonExistent', 'error'); // 属性不存在
// setProperty(child2Scope, 'foo', 123); // 值类型不匹配(需要string)

内容的提问来源于stack exchange,提问作者Lance Pollard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 20:15:29