如何在打字稿中动态声明类型保护?
How to declare type guard dynamically in typescript?
我想根据数组元素动态设置类型保护
正在研究打字稿中的策略模式。这是代码
class StrategyManager {
strategies: Strategy[]
constructor() {
this.strategies = []
}
addStrategy(strategy: Strategy) {
this.strategies.push(strategy)
}
getStrategy(name: <dynamic>) { // this is where I need dynamic type guard
return this.strategies.find((strategy) => strategy.name === name)
}
}
假设策略是这样添加的:
const sm = new StrategyManager()
sm.addStrategy({name:'foo'})
sm.addStrategy({name:'bar'})
然后;
同时使用 sm.getStrategy
获得 strategy
。我需要 name
类型的参数 'foo' | 'bar'
因此 intellisense 会抛出如下错误:
sm.getStrategy('baz') // Intellisense error: `Argument of type '"baz"' is not assignable to parameter of type '"foo" | "bar"'`
无法按照描述完成。
Typescript 无法跟踪您添加的所有可能内容,因为它并不是真正的 运行 您的代码。如果在 运行 时可以使用任何字符串值,那么 typescript 可能无法帮助您约束该类型。
如果你有呢?
sm.addStrategy(await getAsyncStrategyFromServer())
类型系统无法知道它会有什么名字,因为它在编译时无法知道。编译器无法帮助您处理编译器不知道的事情。
你得想想什么是编译时类型错误,什么是运行时间错误
在这种情况下,在编译时,string
是任何有策略名称的地方的正确类型。这是因为,如您所说,名称可以是任何字符串。
但是如果你得到一个没有被添加的策略名称是运行时间错误,因为策略是动态添加的在运行时间。这意味着您使用逻辑而不是类型系统来处理该错误。
getStrategy(name: string) {
const strategy = this.strategies.find((strategy) => strategy.name === name)
if (strategy) {
return strategy
} else {
// or something.
throw new Error(`Strategy with name: ${name} not found`)
}
}
受@ChisBode 评论的启发,如果您按如下方式更改实现,则可以实现此目的。
不是使用 mutable 对象通过连续的突变构建数组 value,您可以将策略管理器设计为 不可变 对象,通过连续转换构建数组类型。
这是一个工作原型:
class StrategyManager<N extends Strategy['name'] = never> {
strategies: (Strategy & { name: N })[] = [];
withStrategy<S extends Strategy>(strategy: S): StrategyManager<N | S['name']> {
const result = new StrategyManager<N | S['name']>();
result.strategies = [...this.strategies, strategy];
return result;
}
getStrategy<T extends N>(name: T) {
return this.strategies.find(
(strategy): strategy is typeof strategy & { name: T } => strategy.name === name
);
}
}
new StrategyManager()
.withStrategy({ name: 'bar' })
.getStrategy('foo')?.name // error as desired
new StrategyManager()
.withStrategy({ name: 'bar' })
.getStrategy('bar')?.name // ok; typeof name is 'bar' | undefined
new StrategyManager()
.withStrategy({ name: 'bar' })
.withStrategy({ name: 'foo' })
.getStrategy('foo')?.name // ok; typeof name is 'foo' | undefined
type Strategy = { name: 'foo' | 'bar' };
备注:
每个 withStrategy
调用 returns 一个具有进一步细化类型的新对象。
约束不需要涉及Strategy
类型,可以是任意string
.
由于我们遵循的是不可变设计模式,因此我们确实应该确保不能通过其他方式修改管理器下的策略数组。为此,我们可以从 class 过渡到工厂,通过闭包获得硬隐私并减少我们必须编写的代码量作为奖励:
function strategyManager<N extends Strategy['name'] = never>(
strategies: (Strategy & { name: N })[] = []
) {
return {
withStrategy<S extends Strategy>(strategy: S) {
return strategyManager<N | S['name']>([...strategies, strategy]);
},
getStrategy<T extends N>(name: T) {
return strategies.find(
(strategy): strategy is typeof strategy & { name: T } => strategy.name === name
);
}
};
}
strategyManager()
.withStrategy({ name: 'bar' })
.getStrategy('foo')?.name // error as desired
strategyManager()
.withStrategy({ name: 'bar' })
.getStrategy('bar')?.name // ok; typeof name is 'bar' | undefined
strategyManager()
.withStrategy({ name: 'bar' })
.withStrategy({ name: 'foo' })
.getStrategy('foo')?.name // ok; typeof name is 'foo' | undefined
type Strategy = { name: 'foo' | 'bar' };
您也可以通过 Stage 3 ECMAScript private fields proposal 实现封装,但是在更多环境中支持闭包,并且简单且经过实战检验。
我想根据数组元素动态设置类型保护
正在研究打字稿中的策略模式。这是代码
class StrategyManager {
strategies: Strategy[]
constructor() {
this.strategies = []
}
addStrategy(strategy: Strategy) {
this.strategies.push(strategy)
}
getStrategy(name: <dynamic>) { // this is where I need dynamic type guard
return this.strategies.find((strategy) => strategy.name === name)
}
}
假设策略是这样添加的:
const sm = new StrategyManager()
sm.addStrategy({name:'foo'})
sm.addStrategy({name:'bar'})
然后;
同时使用 sm.getStrategy
获得 strategy
。我需要 name
类型的参数 'foo' | 'bar'
因此 intellisense 会抛出如下错误:
sm.getStrategy('baz') // Intellisense error: `Argument of type '"baz"' is not assignable to parameter of type '"foo" | "bar"'`
无法按照描述完成。
Typescript 无法跟踪您添加的所有可能内容,因为它并不是真正的 运行 您的代码。如果在 运行 时可以使用任何字符串值,那么 typescript 可能无法帮助您约束该类型。
如果你有呢?
sm.addStrategy(await getAsyncStrategyFromServer())
类型系统无法知道它会有什么名字,因为它在编译时无法知道。编译器无法帮助您处理编译器不知道的事情。
你得想想什么是编译时类型错误,什么是运行时间错误
在这种情况下,在编译时,string
是任何有策略名称的地方的正确类型。这是因为,如您所说,名称可以是任何字符串。
但是如果你得到一个没有被添加的策略名称是运行时间错误,因为策略是动态添加的在运行时间。这意味着您使用逻辑而不是类型系统来处理该错误。
getStrategy(name: string) {
const strategy = this.strategies.find((strategy) => strategy.name === name)
if (strategy) {
return strategy
} else {
// or something.
throw new Error(`Strategy with name: ${name} not found`)
}
}
受@ChisBode 评论的启发,如果您按如下方式更改实现,则可以实现此目的。
不是使用 mutable 对象通过连续的突变构建数组 value,您可以将策略管理器设计为 不可变 对象,通过连续转换构建数组类型。
这是一个工作原型:
class StrategyManager<N extends Strategy['name'] = never> {
strategies: (Strategy & { name: N })[] = [];
withStrategy<S extends Strategy>(strategy: S): StrategyManager<N | S['name']> {
const result = new StrategyManager<N | S['name']>();
result.strategies = [...this.strategies, strategy];
return result;
}
getStrategy<T extends N>(name: T) {
return this.strategies.find(
(strategy): strategy is typeof strategy & { name: T } => strategy.name === name
);
}
}
new StrategyManager()
.withStrategy({ name: 'bar' })
.getStrategy('foo')?.name // error as desired
new StrategyManager()
.withStrategy({ name: 'bar' })
.getStrategy('bar')?.name // ok; typeof name is 'bar' | undefined
new StrategyManager()
.withStrategy({ name: 'bar' })
.withStrategy({ name: 'foo' })
.getStrategy('foo')?.name // ok; typeof name is 'foo' | undefined
type Strategy = { name: 'foo' | 'bar' };
备注:
每个
withStrategy
调用 returns 一个具有进一步细化类型的新对象。约束不需要涉及
Strategy
类型,可以是任意string
.由于我们遵循的是不可变设计模式,因此我们确实应该确保不能通过其他方式修改管理器下的策略数组。为此,我们可以从 class 过渡到工厂,通过闭包获得硬隐私并减少我们必须编写的代码量作为奖励:
function strategyManager<N extends Strategy['name'] = never>( strategies: (Strategy & { name: N })[] = [] ) { return { withStrategy<S extends Strategy>(strategy: S) { return strategyManager<N | S['name']>([...strategies, strategy]); }, getStrategy<T extends N>(name: T) { return strategies.find( (strategy): strategy is typeof strategy & { name: T } => strategy.name === name ); } }; } strategyManager() .withStrategy({ name: 'bar' }) .getStrategy('foo')?.name // error as desired strategyManager() .withStrategy({ name: 'bar' }) .getStrategy('bar')?.name // ok; typeof name is 'bar' | undefined strategyManager() .withStrategy({ name: 'bar' }) .withStrategy({ name: 'foo' }) .getStrategy('foo')?.name // ok; typeof name is 'foo' | undefined type Strategy = { name: 'foo' | 'bar' };
您也可以通过 Stage 3 ECMAScript private fields proposal 实现封装,但是在更多环境中支持闭包,并且简单且经过实战检验。