在 TypeScript 中,当类型是函数的参数时,有没有办法限制 Partial<T> 类型的 extra/excess 属性?
In TypeScript, is there a way to restrict extra/excess properties for a Partial<T> type when the type is a parameter to a function?
有没有一种标准方法可以让场景 1 出现编译错误,因为没有指定已知属性,就像场景 2 一样?或者有什么解决方法吗?
class Class2 {
g: number;
}
class Testing {
static testIt3<T>(val: Partial<T>): void {
}
}
const test = {
g: 6,
a: '6',
};
// Scenario 1
Testing.testIt3<Class2>(test);
// TS does not show any errors for this scenario
// Scenario 2
Testing.testIt3<Class2>({
g: 6,
a: '6',
});
// but it does for this scenario:
// Object literal may only specify known properties...
类型系统不适合对额外对象键的此类限制。 TypeScript 中的类型不是 exact:如果一个对象是 A
类型,并且你向它添加了更多 A
定义中未提及的属性,该对象仍然是类型A
。这基本上是支持 class 继承所必需的,其中 subclasses 可以向 superclasses 添加属性。
唯一一次编译器将类型视为精确类型是在您使用 "fresh" 对象字面量(即尚未分配给任何对象的字面量)并将其传递给需要对象类型。这称为 excess property checking ,它是一种解决语言中缺少确切类型的方法。您希望对 "non-fresh" 对象(例如 test
)进行额外的 属性 检查,但这不会发生。
TypeScript 没有精确类型的具体表示;您不能使用类型 T
并从中生成 Exact<T>
。但是您可以使用 generic constraint 来获得这种效果。给定一个类型 T
,以及一个类型为 U
的对象,您希望它符合无法表示的 Exact<T>
类型,您可以这样:
type Exactly<T, U extends T> = {[K in keyof U]: K extends keyof T ? T[K] : never};
type IsExactly<T, U extends T> = U extends Exactly<T, U> ? true : false;
const testGood = {
g: 1
}
type TestGood = IsExactly<Class2, typeof testGood>; // true
const testBad = {
g: 6,
a: '6',
};
type TestBad = IsExactly<Class2, typeof testBad>; // false
所以编译器能够分辨出 typeof testGood
是“Exactly<Class2, typeof testGood>
”,而 typeof testBad
不是 Exactly<Class2, typeof testBad>
。我们可以用它来构建一个通用函数来做你想做的事。 (在你的情况下,你想要 ExactlyPartial<T, U>
而不是 Exactly<T, U>
,但它非常相似......只是不要限制 U
扩展 T
)。
不幸的是,您的函数在 T
中已经是泛型了,类型要精确。而你手动指定T
,泛型函数需要推断U
的类型。 TypeScript 不允许 partial type parameter inference。您必须手动指定函数中的所有类型参数,或者必须让编译器推断函数中的所有类型参数。所以有解决方法:
一个是将您的函数拆分为 curried 函数,其中第一个泛型函数让您指定 T
,returned 泛型函数推断 U
.它看起来像这样:
class Testing {
static testIt<T>(): <U extends { [K in keyof U]:
K extends keyof T ? T[K] : never
}> (val: U) => void {
return () => { }
}
}
Testing.testIt<Class2>()(testBad); // error, prop "a" incompatible
Testing.testIt<Class2>()(testGood); // okay
这按您预期的方式工作,但会影响运行时,因为您必须在运行时无缘无故地调用柯里化函数。
另一种解决方法是将一个值传递给函数,从中可以推断出 T
。由于您不需要这样的值,因此这实际上是一个虚拟参数。同样,它对运行时有影响,因为您必须传入一个未使用的值。 (您提到您实际上可能在运行时使用这样的值,在这种情况下,这不再是一种解决方法,而是建议的解决方案,因为无论如何您都需要传递一些东西,并且 T
的手动规范在你的代码示例是一条红鲱鱼。)它看起来像这样:
class Testing {
static testIt<T, U extends { [K in keyof U]:
K extends keyof T ? T[K] : never
}>(ctor: new (...args: any) => T, val: U) {
// not using ctor in here, so this is a dummy value
}
}
Testing.testIt(Class2, testBad); // error, prop "a" incompatible
Testing.testIt(Class2, testGood); // okay
我能想到的第三个解决方法是只使用类型系统来表示柯里化函数 return 的结果而不实际调用它。它完全没有运行时影响,这使得它更易于为现有的 JS 代码提供类型,但使用起来有点笨拙,因为您必须断言 Testing.testIt
以正确的方式行事。它看起来像这样:
interface TestIt<T> {
<U extends { [K in keyof U]: K extends keyof T ? T[K] : never }>(val: U): void;
}
class Testing {
static testIt(val: object) {
// not using ctor in here, so this is a dummy value
}
}
(Testing.testIt as TestIt<Class2>)(testBad); // error, prop "a" incompatible
(Testing.testIt as TestIt<Class2>)(testGood); // okay
好的,希望其中一个对您有用。祝你好运!
有没有一种标准方法可以让场景 1 出现编译错误,因为没有指定已知属性,就像场景 2 一样?或者有什么解决方法吗?
class Class2 {
g: number;
}
class Testing {
static testIt3<T>(val: Partial<T>): void {
}
}
const test = {
g: 6,
a: '6',
};
// Scenario 1
Testing.testIt3<Class2>(test);
// TS does not show any errors for this scenario
// Scenario 2
Testing.testIt3<Class2>({
g: 6,
a: '6',
});
// but it does for this scenario:
// Object literal may only specify known properties...
类型系统不适合对额外对象键的此类限制。 TypeScript 中的类型不是 exact:如果一个对象是 A
类型,并且你向它添加了更多 A
定义中未提及的属性,该对象仍然是类型A
。这基本上是支持 class 继承所必需的,其中 subclasses 可以向 superclasses 添加属性。
唯一一次编译器将类型视为精确类型是在您使用 "fresh" 对象字面量(即尚未分配给任何对象的字面量)并将其传递给需要对象类型。这称为 excess property checking ,它是一种解决语言中缺少确切类型的方法。您希望对 "non-fresh" 对象(例如 test
)进行额外的 属性 检查,但这不会发生。
TypeScript 没有精确类型的具体表示;您不能使用类型 T
并从中生成 Exact<T>
。但是您可以使用 generic constraint 来获得这种效果。给定一个类型 T
,以及一个类型为 U
的对象,您希望它符合无法表示的 Exact<T>
类型,您可以这样:
type Exactly<T, U extends T> = {[K in keyof U]: K extends keyof T ? T[K] : never};
type IsExactly<T, U extends T> = U extends Exactly<T, U> ? true : false;
const testGood = {
g: 1
}
type TestGood = IsExactly<Class2, typeof testGood>; // true
const testBad = {
g: 6,
a: '6',
};
type TestBad = IsExactly<Class2, typeof testBad>; // false
所以编译器能够分辨出 typeof testGood
是“Exactly<Class2, typeof testGood>
”,而 typeof testBad
不是 Exactly<Class2, typeof testBad>
。我们可以用它来构建一个通用函数来做你想做的事。 (在你的情况下,你想要 ExactlyPartial<T, U>
而不是 Exactly<T, U>
,但它非常相似......只是不要限制 U
扩展 T
)。
不幸的是,您的函数在 T
中已经是泛型了,类型要精确。而你手动指定T
,泛型函数需要推断U
的类型。 TypeScript 不允许 partial type parameter inference。您必须手动指定函数中的所有类型参数,或者必须让编译器推断函数中的所有类型参数。所以有解决方法:
一个是将您的函数拆分为 curried 函数,其中第一个泛型函数让您指定 T
,returned 泛型函数推断 U
.它看起来像这样:
class Testing {
static testIt<T>(): <U extends { [K in keyof U]:
K extends keyof T ? T[K] : never
}> (val: U) => void {
return () => { }
}
}
Testing.testIt<Class2>()(testBad); // error, prop "a" incompatible
Testing.testIt<Class2>()(testGood); // okay
这按您预期的方式工作,但会影响运行时,因为您必须在运行时无缘无故地调用柯里化函数。
另一种解决方法是将一个值传递给函数,从中可以推断出 T
。由于您不需要这样的值,因此这实际上是一个虚拟参数。同样,它对运行时有影响,因为您必须传入一个未使用的值。 (您提到您实际上可能在运行时使用这样的值,在这种情况下,这不再是一种解决方法,而是建议的解决方案,因为无论如何您都需要传递一些东西,并且 T
的手动规范在你的代码示例是一条红鲱鱼。)它看起来像这样:
class Testing {
static testIt<T, U extends { [K in keyof U]:
K extends keyof T ? T[K] : never
}>(ctor: new (...args: any) => T, val: U) {
// not using ctor in here, so this is a dummy value
}
}
Testing.testIt(Class2, testBad); // error, prop "a" incompatible
Testing.testIt(Class2, testGood); // okay
我能想到的第三个解决方法是只使用类型系统来表示柯里化函数 return 的结果而不实际调用它。它完全没有运行时影响,这使得它更易于为现有的 JS 代码提供类型,但使用起来有点笨拙,因为您必须断言 Testing.testIt
以正确的方式行事。它看起来像这样:
interface TestIt<T> {
<U extends { [K in keyof U]: K extends keyof T ? T[K] : never }>(val: U): void;
}
class Testing {
static testIt(val: object) {
// not using ctor in here, so this is a dummy value
}
}
(Testing.testIt as TestIt<Class2>)(testBad); // error, prop "a" incompatible
(Testing.testIt as TestIt<Class2>)(testGood); // okay
好的,希望其中一个对您有用。祝你好运!