如何在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
相关产品推荐
相关产品推荐

