在 TypeScript 中,当类型是函数的参数时,有没有办法限制 Partial<T> 类型的额外/多余属性?
Posted
技术标签:
【中文标题】在 TypeScript 中,当类型是函数的参数时,有没有办法限制 Partial<T> 类型的额外/多余属性?【英文标题】: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? 【发布时间】:2020-03-10 20:09:36 【问题描述】:是否有一种标准方法可以让场景 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...
Live code
【问题讨论】:
我认为没有办法解决这个问题; TypeScript 没有exact types,而excess property checks 仅适用于场景2中的对象文字。如果您需要手动指定T
,那么您需要partial type parameter inference之类的东西来做您正在寻找的事情,也不支持。
如果你对currying 没问题,我猜你可以做类似this 的事情。如果你想让我把它写下来作为答案,请告诉我。
@jcalz 是的,我刚刚发现this github issue 可以解释它
@jcalz 所以该类型必须在您在示例中声明的函数中内联定义?
我不确定我是否理解这个问题。我的示例取决于有两种泛型类型:您手动指定为 Class2
的 T
类型(否则不可能禁止任何事情,因为对于 some T
,任何对象都是 Partial<T>
);以及根据val
的类型推断出的U
类型。通用约束 U extends [K in keyof U]: K extends keyof T ? T[K] : never
明确强制 val
的所有属性都需要来自 T
类型并且不能是额外的。如果满足您的需求,我很乐意将其写出来。
【参考方案1】:
类型系统不适用于对额外对象键的此类限制。 TypeScript 中的类型不是exact:如果一个对象是A
类型,并且你添加了更多A
的定义中没有提到的属性,那么该对象仍然是A
类型。这本质上是支持类继承所必需的,其中子类可以向超类添加属性。
编译器唯一将类型视为精确的情况是当您使用“新”对象字面量(即尚未分配给任何东西的字面量)并将其传递给需要对象类型的东西时。这被称为excess property checking,它是一种解决语言中缺少确切类型的方法。您希望对像 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
,返回的泛型函数推断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
这可以按您的预期工作,但会影响运行时,因为您必须在运行时无缘无故地调用 curried 函数。
另一种解决方法是传递一个值,从中可以推断出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
我能想到的第三种解决方法是只使用类型系统来表示柯里化函数返回的结果,而不实际调用它。它完全没有运行时影响,这使得它更适合为现有的 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
好的,希望其中一个对您有用。祝你好运!
Link to code
【讨论】:
是的,我只是声明了类型,而不是在我的初始示例中将其作为第一个参数传递,因为我认为这将是一个更简单的示例来显示我遇到的问题。我没有考虑到它可能会影响如何最好地设计函数。以上是关于在 TypeScript 中,当类型是函数的参数时,有没有办法限制 Partial<T> 类型的额外/多余属性?的主要内容,如果未能解决你的问题,请参考以下文章