Typescript:声明具有精确接口类型参数的函数,而不是其派生类型之一

时间:2017-09-10 20:38:52

标签: validation typescript inheritance interface

interface I {
    a: number;
}

interface II extends I {
    b: number;
}

function f(arg: I) : void {
    // do something with arg without trimming the extra properties (logical error)
    console.log(arg);
}

const obj: II = { a:4, b:3 };
f(obj);

我想要做的是让函数f只接受I类型的对象而不是II类型的对象或任何其他派生接口

4 个答案:

答案 0 :(得分:2)

由于打字稿的工作方式很困难。您可以做的是向基础添加type字段,派生接口将覆盖该字段。然后将函数限制为仅显式接受基数:

interface IFoo<T extends string = "foo"> {
  type: T;
}

interface IBar extends IFoo<"bar"> {
}

function ray(baseOnly: IFoo<"foo">) {
}

let foo: IFoo = { type: "foo" };
let bar: IBar = { type: "bar" };

ray(foo); // OK!
ray(bar); // error

和输出错误:

[ts]
Argument of type 'IBar' is not assignable to parameter of type 'IFoo<"foo">'.
  Types of property 'type' are incompatible.
    Type '"bar"' is not assignable to type '"foo"'.

答案 1 :(得分:1)

你无法在Typescript中实现这一点,一般来说,在大多数语言中你不能做出这样的约束。面向对象编程的一个原则是您可以将派生类传递给期望基类的位置。您可以执行运行时检查,如果找到您不期望的成员,则可能会抛出错误。但是编译器无法帮助您实现这一目标。

答案 2 :(得分:0)

另一种可能性是放弃接口并使用具有私有属性和私有构造函数的类。这些不鼓励扩展:

export class I {
  private clazz: 'I'; // private field
  private constructor(public a: number) { 
    Object.seal(this); // if you really don't want extra properties at runtime
  }
  public static make(a: number): I {
    return new I(a); // can only call new inside the class
  }
}

let i = I.make(3);
f(i); // okay

您无法创建I作为对象文字:

i = { a: 2 }; // error, isn't an I
f({a: 2}); // error, isn't an I

你不能将它子类化:

class II extends I { // error, I has a private constructor
  b: number;
}

可以通过界面扩展它:

interface III extends I {
  b: number;
}
declare let iii: III;

可以调用扩展接口上的函数

f(iii); 

但你仍然无法用对象文字创建一个

iii = { a: 1, b: 2 }; // error

或使用解构(也创建一个新对象),

iii = { ...I.make(1), b: 2 };

,所以这比使用接口更安全一些。

对于狡猾的开发者来说,有很多方法可以解决这个问题。您可以通过Object.assign()使TypeScript生成子类,但如果在Object.seal()的构造函数中使用I,则至少可以在运行时出错:

iii = Object.assign(i, { b: 17 }); // no error at compile time, error at runtime

您可以随时使用any使类型系统静音,(尽管如此,您可以在instanceof内使用f()保护在运行时导致错误。

iii = { a: 1, b: 2 } as any; // no error
f(iii); // no error at compile time, maybe error if f() uses instanceof

希望有所帮助;祝你好运!

答案 3 :(得分:0)

这对我有用(无论如何在ts 3.3上):

// Checks that B is a subset of A (no extra properties)
type Subset<A extends {}, B extends {}> = {
   [P in keyof B]: P extends keyof A ? (B[P] extends A[P] | undefined ? A[P] : never) : never;
}

// Type for function arguments
type Strict<A extends {}, B extends {}> = Subset<A, B> & Subset<B, A>;
// E.g.
type BaseOptions = { a: string, b: number }
const strict = <T extends Strict<BaseOptions, T>>(options: T) => { }
strict({ a: "hi", b: 4 })        //Fine
strict({ a: 5, b: 4 })           //Error
strict({ a: "o", b: "hello" })   //Error
strict({ a: "o" })               //Error
strict({ b: 4 })                 //Error
strict({ a: "o", b: 4, c: 5 })   //Error

// Type for variable declarations
type Exact<A extends {}> = Subset<A, A>;
// E.g.
const options0: Exact<BaseOptions> = { a: "hi", b: 4 }        //Fine
const options1: Exact<BaseOptions> = { a: 5, b: 4 }           //Error
const options2: Exact<BaseOptions> = { a: "o", b: "hello" }   //Error
const options3: Exact<BaseOptions> = { a: "o" }               //Error
const options4: Exact<BaseOptions> = { b: 4 }                 //Error
const options5: Exact<BaseOptions> = { a: "o", b: 4, c: 5 }   //Error

// Beware of using Exact for arguments:
// For inline arguments it seems to work correctly:
exact({ a: "o", b: 4, c: 5 })   //Error
strict({ a: "o", b: 4, c: 5 })   //Error
// But it doesn't work for arguments coming from variables:
const options6 = { a: "o", b: 4, c: 5 }
exact(options6) // Fine -- Should be error
strict(options6)  //Error -- Is correctly error

您可以在我的评论here中看到更多详细信息。

因此适用于您的示例:

interface I { a: number; }

interface II extends I { b: number; }

function f<T extends Strict<I, T>>(arg: T): void {
   // do something with arg without trimming the extra properties (logical error)
   console.log(arg);
}
const obj1: I = { a: 4 };
const obj2: II = { a: 4, b: 3 };
f(obj1); // Fine
f(obj2); // Error