如何在记录类型上限制键?

huangapple go评论81阅读模式
英文:

How do you restrict keys on a Record type?

问题

如何限制记录/对象上的键?

在下面的示例中,如果您传递给函数的对象包含在我的 DIMENSIONS 对象中存在的键或值,我希望能看到一个 TypeScript 错误。

我可以在值上生成一个错误,但我尝试过的所有方法都无法在键上生成错误。

const DIMENSIONS = {
  userType: 1,
  moduleVersion: 2
} as const;

type DimensionKeys = keyof typeof DIMENSIONS;
type DimensionValues = typeof DIMENSIONS[DimensionKeys];
type x = typeof DIMENSIONS;

function process<T extends string, V extends number>(yourDimensions: Record<Exclude<T, DimensionKeys>, Exclude<V, DimensionValues>>){
    return {...yourDimensions, ...DIMENSIONS};
}

// ----------------------------------------------
//             好的,继续
// ----------------------------------------------
const myCorrectDimensions = {
  country: 33
} as const;
const combinedDimensions = process(myCorrectDimensions);

// ----------------------------------------------
//        不好意思,那个值已被保留
// ----------------------------------------------
const myInvalidValueDimensions = {
  country: 1
} as const;
const badDimensions = process(myInvalidValueDimensions);

// ----------------------------------------------
//        不好意思,那个键已被保留
// ----------------------------------------------
const myInvalidKeyDimensions = {
  userType: 79
} as const;
const badDimensions2 = process(myInvalidKeyDimensions);
英文:

How do you restrict keys on a Record/object?

In the example below, I want to see a ts error if the object you pass to a function contains a key or value present in my DIMENSIONS object.

I can make an error on a value, but nothing I've tried works on a key.

const DIMENSIONS = {
  userType: 1,
  moduleVersion: 2
} as const;

type DimensionKeys = keyof typeof DIMENSIONS;
type DimensionValues = typeof DIMENSIONS[DimensionKeys];
type x = typeof DIMENSIONS;

function process&lt;T extends string, V extends number&gt;(yourDimensions: Record&lt;Exclude&lt;T, DimensionKeys&gt;, Exclude&lt;V,DimensionValues&gt;&gt;){
    return {...yourDimensions, ...DIMENSIONS};
}

// ----------------------------------------------
//             cool, go ahead
// ----------------------------------------------
const myCorrectDimensions = {
  country: 33
} as const;
const combinedDimensions = process(myCorrectDimensions);

// ----------------------------------------------
//        Na mate, that value is reserved
// ----------------------------------------------
const myInvalidValueDimensions = {
  country: 1
} as const;
const badDimensions = process(myInvalidValueDimensions);

// ----------------------------------------------
//        Na mate, that key is reserved
// ----------------------------------------------
const myInvalidKeyDimensions = {
  userType: 79
} as const;
const badDimensions2 = process(myInvalidKeyDimensions);

答案1

得分: 1

以下是您要翻译的代码部分:

One approach looks like this:

    function process<T extends Record<keyof T, number>>(
      yourDimensions: { [K in keyof T]:
        K extends DimensionKeys ? never :
        T[K] extends DimensionValues ? never :
        T[K]
      }) {
      return { ...yourDimensions, ...DIMENSIONS };

Here we have a single objectlike [generic](https://www.typescriptlang.org/docs/handbook/2/generics.html) type parameter `T` corresponding to the type of the function parameter `yourDimensions`.  It is [constrained](https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-constraints) to `Record<keyof T, number>`, meaning it can have any keys at all, but its values must be assignable to `number`.

Then, the actual type of `yourDimensions` is the [mapped type](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html) `{[K in keyof T]: K extends DimensionKeys ? never : T[K] extends DimensionValues ? never : T[K]}`.  TypeScript will infer `T` to be the type of `yourDimensions`, but then check it against that mapped type.  For each key `K` from the keys of `T`, the accepted value type `K extends DimensionKeys ? never : T[K] extends DimensionValues ? never : T[K]` is a [conditional type](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html) that evaluates to to the input property type `T[K]` if and only if the input property does not use a key of `DimensionKeys` or a value of `DimensionValues`.  Otherwise it will be of type `never`, which is going to be an error (there are no values of type `never`).

Let's test it:

    const example = process({
      a: 3, // okay
      b: 2, // error
      moduleVersion: 3 // error
    })
    
    /* function process<{
        a: 3;
        b: 2;
        moduleVersion: number;
    }>(yourDimensions: {
        a: 3;
        b: never;
        moduleVersion: never;
    }) */

Here the compiler infers `T` to be something like `{a: 3, b: 2, moduleVersion: number}`, so the type of the `yourDimensions` parameter is `{a: 3, b: never, moduleVersion: never}`.  But the input `{a: 3, b: 2, moduleVersion: 3}` doesn't match that type.  The `a` property is fine, but the other two cause errors because `2` and `3` are not of type `never`.

[Playground link to code](https://www.typescriptlang.org/play?target=99&amp;jsx=0&amp;ts=5.1.3#code/MYewdgzgLgBAIgSQLIFEByBlBB5TMC8MA3gFAwwCuEApgE4AqAngA7UBcMAjADRkwC2IACYUANtQBqdCAEtwHAEwkAvjACGEGKEhQA3CRJQW1eDP7VIcsAGlqjTYQDWdkADMYR1m-jJ0WXBj6niZwZhay4BJqohTUDh7G3oiomDiYANqh5pbgtvYAukHGMAAeBAle7sl+aYEGrhRgwFBWMMy0IMBxEAA89DDUJVAWQpoAStSgtEI9zoze9NwwYBT8AEZ0AHybABR88xS0WeFWEBxEMOnWMDJgMHML+Wx85NeDw2CjptkRNnaaAH5ltQAG50GDPcjkehXfIDIYjTTHHJgKIxOIwIFgUHgyFQmHWfJ8ZQASmIfFo1CghzuFwAdAyDkcwiiIEsGXTqqkAjBlPplAYAPSCmAAWnFEslUulMtlctFJGFUOVKq0IBAoiWAHMQOoABbUNRCRUi+Vm80WkjaaACRgAYRAtEpzWRv3ipHIoEaUFojA4AGZ-Sp1JprXoreAbaB1rdqEJXadyu1Ot0dvx7Y7nVAE5GSfoTWKLUXixKC6qYGg1AI1MMllA9TWYCDorEbppKTRaGDjUqS33LWHbQgwM3RDIhGjYjnIOUPWrvb6OJxgxo1Tp9IO1kbp-Fk10IBA04xh6Px5PqDu80LTf3b7Ky6rK9Xax4G7A5m2YB26N2C3f-1KEY6EOI7ROOeQ7rOfBUHQTCsBwADsACcK6hpG4abtuLJugoSYdPuh7pieYFCBB2GnFeBiDoMaj8Mw4h4SmB47HOagBrw5BrIoHECMIYiSNIVgBioJJCgAVDADRNC04BtPh3Q9HO5BsTA-r6MqXEwAo6lQoIIjiFItC-BwKzrHQ-K7EyO7nC86gBjpnEmTitAObx+kCUZQnAmCLkiTZyoqWptmadiPmuXp-GGcZ3nmcGABk5LKpSRrgKIjCUJ2cHsFwrnJUIqXpRFBmCfIWn8jAYmCkAA)
英文:

One approach looks like this:

function process&lt;T extends Record&lt;keyof T, number&gt;&gt;(
yourDimensions: { [K in keyof T]:
K extends DimensionKeys ? never :
T[K] extends DimensionValues ? never :
T[K]
}) {
return { ...yourDimensions, ...DIMENSIONS };

Here we have a single objectlike generic type parameter T corresponding to the type of the function parameter yourDimensions. It is constrained to Record&lt;keyof T, number&gt;, meaning it can have any keys at all, but its values must be assignable to number.

Then, the actual type of yourDimensions is the mapped type {[K in keyof T]: K extends DimensionKeys ? never : T[K] extends DimensionValues ? never : T[K]}. TypeScript will infer T to be the type of yourDimensions, but then check it against that mapped type. For each key K from the keys of T, the accepted value type K extends DimensionKeys ? never : T[K] extends DimensionValues ? never : T[K] is a conditional type that evaluates to to the input property type T[K] if and only if the input property does not use a key of DimensionKeys or a value of DimensionValues. Otherwise it will be of type never, which is going to be an error (there are no values of type never).

Let's test it:

const example = process({
a: 3, // okay
b: 2, // error
moduleVersion: 3 // error
})
/* function process&lt;{
a: 3;
b: 2;
moduleVersion: number;
}&gt;(yourDimensions: {
a: 3;
b: never;
moduleVersion: never;
}) */

Here the compiler infers T to be something like {a: 3, b: 2, moduleVersion: number}, so the type of the yourDimensions parameter is {a: 3, b: never, moduleVersion: never}. But the input {a: 3, b: 2, moduleVersion: 3} doesn't match that type. The a property is fine, but the other two cause errors because 2 and 3 are not of type never.

Playground link to code

huangapple
  • 本文由 发表于 2023年6月29日 03:19:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76576131.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定