Typescript函数的继承和重载字符串文字参数

时间:2018-06-25 05:50:26

标签: typescript

我目前正在尝试向依赖继承的库中添加一些类型。总体层次结构类似于:

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"'

TS Playground link

当前解决方案的错误是Interface 'ValidationTextBox' incorrectly extends interface 'TextBox'.,因为getObj(...)中的“ B”不能分配给“ C”。

帮助表示感谢!

2 个答案:

答案 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 });

TS Playground link

解决方案的关键是_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接口上的泛型来解决。这不是最干净的解决方案,但很少见,对我的需求没有太大影响。