Validation Typescript:声明函数,该函数接受确切接口类型的参数,而不是其派生类型之一

Validation Typescript:声明函数,该函数接受确切接口类型的参数,而不是其派生类型之一,validation,typescript,inheritance,interface,Validation,Typescript,Inheritance,Interface,我想做的是使函数f只接受类型I的对象,而不接受类型II或任何其他派生接口在Typescript中无法实现这一点,一般来说,在大多数语言中都无法进行这样的约束。面向对象编程的一个原则是,可以在需要基类的地方传递派生类。您可以执行运行时检查,如果您发现您不期望的成员,您可以抛出一个错误。但是编译器不会帮助您实现这一点 在Typescript中无法实现这一点,一般来说,在大多数语言中,您都无法进行这样的约束。面向对象编程的一个原则是,可以在需要基类的地方传递派生类。您可以执行运行时检查,如果您发现您不

我想做的是使函数
f
只接受类型
I
的对象,而不接受类型
II
或任何其他派生接口在Typescript中无法实现这一点,一般来说,在大多数语言中都无法进行这样的约束。面向对象编程的一个原则是,可以在需要基类的地方传递派生类。您可以执行运行时检查,如果您发现您不期望的成员,您可以抛出一个错误。但是编译器不会帮助您实现这一点

在Typescript中无法实现这一点,一般来说,在大多数语言中,您都无法进行这样的约束。面向对象编程的一个原则是,可以在需要基类的地方传递派生类。您可以执行运行时检查,如果您发现您不期望的成员,您可以抛出一个错误。但是编译器不会帮助您实现这一点

因为打字脚本的工作方式而很难。您可以做的是向基添加一个
类型
字段,派生接口将覆盖该字段。然后,要将函数限制为仅显式接受基函数,请执行以下操作:

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);
interface III extends I {
  b: number;
}
declare let iii: III;
接口IFoo{
类型:T;
}
接口IBar扩展了IFoo{
}
函数射线(仅baseOnly:IFoo){
}
让foo:IFoo={type:“foo”};
let bar:IBar={type:“bar”};
雷(福);//好啊
射线(条形);//错误
和输出错误:

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]
“IBar”类型的参数不能分配给“IFoo”类型的参数。
属性“type”的类型不兼容。
类型“bar”不可分配给类型“foo”。

由于typescript的工作方式,这很难。您可以做的是向基添加一个
类型
字段,派生接口将覆盖该字段。然后,要将函数限制为仅显式接受基函数,请执行以下操作:

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);
接口IFoo{
类型:T;
}
接口IBar扩展了IFoo{
}
函数射线(仅baseOnly:IFoo){
}
让foo:IFoo={type:“foo”};
let bar:IBar={type:“bar”};
雷(福);//好啊
射线(条形);//错误
和输出错误:

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]
“IBar”类型的参数不能分配给“IFoo”类型的参数。
属性“type”的类型不兼容。
类型“bar”不可分配给类型“foo”。

另一种可能性是放弃接口,使用具有私有属性和私有构造函数的类。这些限制了扩展:

[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"'.
不能将
I
创建为对象文字:

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 = { 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); 
或者使用分解(也会创建新对象)

,所以这至少比使用接口安全一些


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

iii = { ...I.make(1), b: 2 };
您可以始终使用
any
使类型系统静音(不过,您也可以在
f()
中使用
instanceof
guard来在运行时导致错误)



希望有帮助;祝你好运

另一种可能性是放弃接口,使用具有私有属性和私有构造函数的类。这些限制了扩展:

[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"'.
不能将
I
创建为对象文字:

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 = { 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); 
或者使用分解(也会创建新对象)

,所以这至少比使用接口安全一些


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

iii = { ...I.make(1), b: 2 };
您可以始终使用
any
使类型系统静音(不过,您也可以在
f()
中使用
instanceof
guard来在运行时导致错误)


希望有帮助;祝你好运

这对我很有用(不管怎样,在ts 3.3上):

//检查B是否是a的子集(无额外属性)
类型子集

因此适用于您的示例:

// 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

接口I{a:number;}
接口II扩展I{b:number;}
函数f(arg:T):void{
//在不修剪额外属性的情况下使用arg执行某些操作(逻辑错误)
控制台日志(arg);
}
常量obj1:I={a:4};
常数obj2:II={a:4,b:3};
f(obj1);//好的
f(obj2);//错误
这对我来说很有效(无论如何在ts 3.3上):

//检查B是否是a的子集(无额外属性)
类型子集

因此适用于您的示例:

// 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

接口I{a:number;}
接口II扩展I{b:number;}
函数f(arg:T):void{
//在不修剪额外属性的情况下使用arg执行某些操作(逻辑错误)
控制台日志(arg);
}
常量obj1:I={a:4};
常数obj2:II={a:4,b:3};
f(obj1);//好的
f(obj2);//错误

我不知道您为什么要这样做,但我能看到的唯一方法是创建单独的接口,而不是扩展接口。@kamyl即使这样,因为typescript使用结构兼容性来确定类型兼容性的方式,无法保证传递的对象仅具有接口类型和接口上指定的字段如果您使用的是ts>3.1,那么现在应该可以了,请检查:我不确定您为什么要这样做,但我能看到的唯一方法是创建单独的接口,而不是扩展接口。@kamyl即使如此,由于typescript使用结构兼容性来确定类型兼容性的方式,它仍然是I