我目前正在尝试向依赖继承的库中添加一些类型。总体层次结构类似于:
BaseWidget --> TextBox --> ValidationTextBox
BaseWidget提供了一个称为getObj(name)
的JS函数,该函数映射到TS中的getObj(name: string): any
并有效地在当前对象上查找名为_get<name>
的函数,然后执行并返回结果。 / p>
这些“属性”在各个类中公开,并且在类之间继承,因此ValidationTextBox可以访问TextBox上的所有属性。我想知道是否可以添加类似于我在下面尝试过的类型,而不必重新定义每个类中的重载。
interface BaseWidget {
getObj(name: string): any
}
interface TextBox extends BaseWidget {
getObj(name: "B"): string
}
interface ValidationTextBox extends TextBox {
getObj(name: "C"): boolean
// getObj(name: "B"): string; // Adding this would make it compile, but obviously not ideal in the least
// getObj(name: string): any // Adding this also compiles, but I lose type information for 'getObj("B")'
}
declare const obj: ValidationTextBox;
console.log(obj.getObj("B")); // Error: Argument of type '"B"' is not assignable to parameter of type '"C"'
当前解决方案的错误是Interface 'ValidationTextBox' incorrectly extends interface 'TextBox'.
,因为getObj(...)
中的“ B”不能分配给“ C”。
帮助表示感谢!
答案 0 :(得分:1)
可能有几种解决方案。最简单的方法是通过指定额外的重载来使编译器满意,而不必使用类型查询来重写它们:
interface ValidationTextBox extends TextBox {
// The extra overload with C, and whatever overloads are in the base class (TextBox['getObj'])
getObj: ((name: "C") => boolean) & TextBox['getObj'];
}
另一种解决方案是使类型通用,以允许我们添加到name
参数的类型中,而无需实际覆盖该方法:
interface BaseWidget<TName = string> {
getObj(name: TName): any
}
// TName can be used to add to the getName overload in derived interfaces
interface TextBox<TName = never> extends BaseWidget<TName | "B"> {
}
// TName can be used to add to the getName overload in derived interfaces
interface ValidationTextBox<TName = never> extends TextBox<TName | "C"> {
}
declare const obj: ValidationTextBox;
console.log(obj.getObj("B"));
console.log(obj.getObj("C"));
答案 1 :(得分:0)
玩了一段时间后,我遇到了另一个非常适合我需求的解决方案:
// --- Properties interface to set up getObj/setObj --- //
interface _Properties<T> {
getObj<U extends keyof T>(name: U): T[U]
setObj<U extends keyof T>(name: U, value: T[U]): void;
setObj<U extends keyof T>(map: { [key in U]: T[key] }): void;
}
// --- Some data class interfaces that the TextBox classes use --- //
interface SomeValue {
a: number;
}
interface MoreSomeValue extends SomeValue {
b: string;
}
// --- Define properties for the classes --- //
interface TextBoxProps<ValueType = string> {
value: ValueType; // Completely changing type of prop in inherited class can (must?) be done via generics
disabled: boolean;
test: SomeValue; // But overriding to return more specific is OK
}
interface ValidationTextBoxProps extends TextBoxProps<number> {
min: number;
max: number;
test: MoreSomeValue
}
// --- Actual class interfaces extending properties --- //
interface TextBox extends _Properties<TextBoxProps> { }
interface ValidationTextBox extends _Properties<ValidationTextBoxProps>, TextBox { }
// --- Constructor that allows properties to be set --- //
interface ValidationTextBoxConstructor {
// Partial since all attributes are optional
new(props: Partial<ValidationTextBoxProps>): ValidationTextBox;
}
declare const ValidationTextBoxConstructor: ValidationTextBoxConstructor; // Actual constructor
// --- Usage --- //
// The following is all type checked at compile time, changing any props/strings/values will cause compile time errors
const obj = new ValidationTextBoxConstructor({ min: 0, max: 5, disabled: true });
console.log(obj.getObj("test"));
obj.setObj("min", 5);
obj.setObj({ min: 5, max: 0, disabled: false });
解决方案的关键是_Properties接口。从此扩展并指定通用类型T使您可以使用getObj(name: string)
,其中name必须是所提供类型的键,并且它将返回T [name]的类型。 setObj(name: string, value: T[U])
同样适用,其中value必须是T [name]指定的类型。第二个setObj
函数还接受{键=>值}的哈希,并在提供的每个键上调用setObj(key,value)。这也可以通过允许T的任何键并将其值设置为T [key]类型来正确检查传入的对象。
* Props接口仅定义可用于它们的属性,并用作_Properties中的类型。如果使用它们的类从另一个扩展_Properties的类扩展,则它们应彼此扩展。
这还提供了一个构造函数,可以选择使用属性名称并进行设置。
唯一的问题是属性是否需要完全更改其类型(例如,基类具有字符串属性,但继承类将其公开为数字),但这可以通过Props接口上的泛型来解决。这不是最干净的解决方案,但很少见,对我的需求没有太大影响。