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
类型的对象或任何其他派生接口
答案 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