从另一个接口中的键的字符串数组创建一个具有固定长度键的记录类型 [英] Create a Record type with fixed length keys from string array of keys in another interface

查看:23
本文介绍了从另一个接口中的键的字符串数组创建一个具有固定长度键的记录类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是最小的代码示例.我有一个名为 Foo 的接口,它具有属性 one ,它接受对象类型的数组.

This is the minimal code example. I have an interface named Foo which has property one which accepts an array of the object type.

interface Foo {
    one: {
        key: string;
       // more properties
    }[]
}

这里key的类型是一个字符串.

Here the type of key is a string.

我想创建一个 Record 类型,其中键是字符串文字联合类型,我们将从 Foo 类型的一个属性的 key 数组中获取,值可以是任何字符串类型.

I want to create a Record type where keys would be of string literal union type which we would take from the array of key of one property in Foo type and values can be any string type.

我在创建该类型时遇到问题.

I am getting issues in creating that type.

人为的例子:

const example: Foo = { 
    one: [{ key: "mykey1" }, {key: "mykey2"}, {key: "mykey3"}]
}

const A = {} as Record<string, string>; // as of now I did it like this
// I need to strongly type this constant.

example.one.map(el => {
  A[el.key] = "anything" // contrived example 
})

推荐答案

将变量注释为 Foo 会丢掉你需要的关键信息

Annotating a variable as Foo throws away the key information you need

这里的主要障碍是当你 使用(非union) 类型,例如 const 示例:Foo = ...,编译器会将该变量视为该类型,而不管您使用什么值对其进行初始化.所以关于初始化值的任何更具体的信息,例如 字符串文字类型 "mykey1",将被丢弃.Foo 的定义只有 string 作为 one 数组元素的 key 属性,所以 <如果您尝试检查变量的类型,您将得到 code>string :

The main obstacle here is that when you annotate a variable with a (non-union) type, like const example: Foo = ..., the compiler will treat that variable as being of that type regardless of what value you initialize it with. And so any more specific information about the initialized value, such as the string literal type "mykey1", will be thrown away. The definition of Foo has just string as the key property of the elements of the one array, and so string is what you'll get if you try to inspect the variable's type:

const example: Foo = {
    one: [{ key: "mykey1" }, { key: "mykey2" }, { key: "mykey3" }]
};
// const example: Foo
type KeyType = typeof example["one"][number]["key"]
// type KeyType = string, oops

所以你真的不想注释example(除非你想手动写出包含所有所需信息的完整类型).相反,您应该尝试让编译器 推断 example 的类型,使其具有您关心的信息,同时仍被视为可分配给 Foo.

So you really don't want to annotate example (unless you want to manually write out the full type with all the information needed). Instead you should try to get the compiler to infer the type of example in such a way that it has the information you care about, while still being seen as assignable to Foo.

简单地省略注释也会丢弃这些关键信息

如果我们只是省略注释,那也是不够的:

If we simply leave off the annotation, that's also not sufficient:

const example = {
    one: [{ key: "mykey1" }, { key: "mykey2" }, { key: "mykey3" }]
};
// const example: {  one: {  key: string; }[]; }
type KeyType = typeof example["one"][number]["key"]
// type KeyType = string, oops

同样,它只是string.这是因为推断变量类型的默认启发式方法倾向于将任何字符串文字属性扩展为 string.有时会重新分配属性,并且编译器不知道您不打算编写 example.one[0].key = "somethingElse".所以 string 是它推断的类型,而不是 "mykey1".我们需要做点别的.

Again, it's just string. That's because the default heuristics for inferring variable types tends to widen any string literal properties to just string. Properties are sometimes reassigned, and the compiler has no idea that you don't intend to write example.one[0].key = "somethingElse". And so string is the type it infers instead of "mykey1". We need to do something else.

const 断言将保留关键信息,但它不再与 Foo 兼容

A const assertion will preserve the key information, but it's not compatible with Foo anymore

如果你想告诉编译器你不会修改 example 的内容并且它应该尝试推断最具体的类型,你可以使用 const 断言在初始化程序上:

If you want to tell the compiler that you are not going to modify the contents of example and that it should try to infer the most specific type possible, you can use a const assertion on the initializer:

const example = {
    one: [{ key: "mykey1" }, { key: "mykey2" }, { key: "mykey3" }]
} as const;
/* const example: { readonly one: readonly [
    { readonly key: "mykey1"; }, 
    { readonly key: "mykey2"; }, 
    { readonly key: "mykey3"; }
    ];
*/

type KeyType = typeof example["one"][number]["key"]
// type KeyType =  "mykey1" | "mykey2" | "mykey3", hooray!

现在 example 被推断为具有 readonly 属性的对象,其中包含一个 readonly 对象数组,具有 readonly> 属性的值是我们关心的字符串文字.所以 KeyType 正是我们想要的字符串文字的联合.

Now example is inferred to be an object with a readonly property containing a readonly array of objects with readonly properties whose values are the string literals we care about. And so KeyType is exactly the union of string literals we want.

那太好了,我们就在这里完成了,除了一个皱纹.example 的推断类型不可分配给您定义的 Foo.事实证明 readonly 数组 不能分配给可变数组(反之亦然),所以会发生这种情况:

That's great, and we'd be done here, except for one wrinkle. The inferred type of example is not assignable to your Foo as defined. It turns out that readonly arrays are not assignable to mutable arrays (it is the other way around), and so this happens:

function acceptFoo(foo: Foo) { }
acceptFoo(example); // error! one is readonly
// -----> ~~~~~~~


也许你应该重新定义 Foo 以兼容 const 断言


Maybe you should redefine Foo to be compatible with a const assertion

您是否打算从 Fooone 属性中添加或删除元素?如果没有,那么这里最简单的解决方案就是重新定义 Foo:

Are you ever going to add or remove elements from a Foo's one property? If not, then the easiest solution here is to just redefine Foo:

interface Foo {
    one: readonly { // <-- change to readonly array type
        key: string;
        // more properties
    }[]
}

acceptFoo(example); // okay now

如果您不想进行这种更改,那么您需要其他一些解决方案.即使你这样做了,去掉注释并使用 as const 也会产生副作用,即 example 真的可能不是一个有效的 Foo,并且直到您稍后尝试将其用作 Foo 时,您才会发现它:

If you don't want to make that change, then you need some other solution. And even if you do, leaving off the annotation and using as const has the side effect that example really might not be a valid Foo, and you wouldn't catch it until you tried to use it as a Foo later:

const example = {
    one: [{ key: "mykey1" }, { key: "mykey2" }, { kee: "mykey3" }]
} as const; // no error here

/* const example: { readonly one: readonly [
    { readonly key: "mykey1"; }, 
    { readonly key: "mykey2"; }, 
    { readonly kee: "mykey3"; }
    ];
*/

acceptFoo(example); // error here

one 的第三个元素有一个 kee 属性而不是 key 属性.这是一个错误,但 const example = ... 行没有错误,因为没有说它必须是 Foo.当您将其视为 Foo 时,您稍后会收到错误.

The third element of one has a kee property instead of a key property. That's a mistake, but there's no error at the const example = ... line because nothing says it has to be a Foo. You get an error later when you treat it like a Foo.

这对您来说可能是可以接受的,也可能不是.如果是这样,那么我们可以停在这里.如果没有,请继续阅读:

This might be acceptable to you, or maybe not. If it is, then we can stop here. If not, read on:

或者你可以创建一个通用的辅助函数来推断一个 Foo 兼容的类型,它也保留了关键信息

Or you can make a generic helper function to infer a Foo-compatible type that also preserves key information

另一个想法而不是使用 as const 是制作一个 通用 辅助函数,用于指导推理过程.在运行时它只会返回它的输入,所以它看起来像一个空操作.这是一种方法:

Another idea instead of using as const is to make a generic helper function that guides the inference process. At runtime it would just return its input, so it looks like a no-op. Here's one way to do it:

interface FooWithKeys<K extends string> extends Foo {
    one: {
        key: K;
        // more properties
    }[]
}
const asFoo = <K extends string>(fwk: FooWithKeys<K>) => fwk;

FooWithKeys 类型是 Foo 的扩展,其中 one 中的键已知为 K,这是约束是一个子类型字符串.类型 FooWithKeys 可以赋值给 Foo,但编译器知道键是 ab 而不仅仅是字符串.

The FooWithKeys<K> type is an extension of Foo where the keys in one are known to be K, which is constrained to be a subtype of string. A type FooWithKeys<"a" | "b"> is assignable to Foo, but the compiler knows that the keys are a or b and not just string.

asFoo() 辅助函数将查看其输入并推断与它一致的 K 类型.由于类型参数 K 被限制为 string,编译器将尽可能为其推断字符串文字类型.

The asFoo() helper function will look at its input and infer a type for K that is consistent with it. Since the type parameter K is constrained to string, the compiler will try to infer string literal types for it if possible.

让我们看看它的实际效果:

Let's see it in action:

const example = asFoo({
    one: [{ key: "mykey1" }, { key: "mykey2" }, { key: "mykey3" }]
})
// const example: FooWithKeys<"mykey1" | "mykey2" | "mykey3">

看起来不错.现在我们可以像以前一样获取密钥类型:

Looks good. Now we can get the key type as before:

type KeyType = typeof example["one"][number]["key"]
// type KeyType =  "mykey1" | "mykey2" | "mykey3", hooray!

并制作您的A字典:

const A = {} as Record<KeyType, string>;
example.one.map(el => {
    A[el.key] = "anything"
})

而且 example 仍然被视为一个 Foo:

And example is still seen as a Foo:

acceptFoo(example); // okay

如果我们在 example 中犯了任何错误,我们就会在那里得到一个错误:

And if we made any mistake with example, we'd get an error right there:

const example = asFoo({
    one: [{ key: "mykey1" }, { key: "mykey2" }, { kee: "mykey3" }] // error!
    // -----------------------------------------> ~~~~~~~~~~~~~
    // Object literal may only specify known properties, and 'kee' does not exist in type
})

Playground 代码链接

这篇关于从另一个接口中的键的字符串数组创建一个具有固定长度键的记录类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆