简体   繁体   English

有没有办法在创建另一个 object 类型时使用 object 类型的特定值

[英]Is there a way to use specific values of an object type when creating another object type

The example is for defining a QuestionMap and AnswerMap .该示例用于定义QuestionMapAnswerMap There are multiple types of questions that produce different types of answers.有多种类型的问题会产生不同类型的答案。 So, in order to define the map of answers, you must know the question.所以,为了定义答案的 map,你必须知道这个问题。 Here's an example:这是一个例子:

const questionMap: QuestionMap = {
  QUESTION_1: { type: 'boolean' },
  QUESTION_2: { type: 'string' }
  QUESTION_3: { type: 'dropdown', options: { CUSTOM: 'custom answer', UNUSED: 'unused answer' }}
}
const answerMap: Answer<QuestionMap> = {
  QUESTION_1: true,
  QUESTION_2: 'custom answer'
}

In this best attempt for the type of AnswerMap , it doesn't use the Answer type for a specific question.在对AnswerMap类型的最佳尝试中,它不会将Answer类型用于特定问题。

type AnswerMap<TQuestionMap extends QuestionMap> = Partial<Record<QuestionId, Answer<TQuestionMap[QuestionId]>>>

What is the correct definition for AnswerMap that properly types each answer based on the question's type?根据问题类型正确键入每个答案的AnswerMap的正确定义是什么?

I'm going to guess about your QuestionMap type... well, actually I'm going to eliminate it because it doesn't help me much in my answer.我要猜测您的QuestionMap类型...好吧,实际上我要消除它,因为它对我的回答没有多大帮助。 First of all, if you want to enforce that answerMap corresponds to the particular questions from questionMap , you should not annotate questionMap at all.首先,如果您想强制answerMap对应于 questionMap 中的特定questionMap ,则根本不应该注释questionMap Doing so would end up widening the type of questionMap to something that forgets entirely about these particulars, much the way saying const str: string = "hello" will forget that str is the string literal "hello" and only remember that it is a string .这样做最终会将questionMap的类型扩大到完全忘记这些细节的东西,就像说const str: string = "hello"会忘记str是字符串文字"hello"并且只记住它是一个string .

In fact, instead of annotating, I suggest using a const assertion so that the compiler keeps track of every single string literal in it:事实上,我建议不要使用注释,而是使用const断言,以便编译器跟踪其中的每个字符串文字:

const questionMap = {
    QUESTION_1: { type: 'boolean' },
    QUESTION_2: { type: 'string' },
    QUESTION_3: { type: 'dropdown', options: { CUSTOM: 'custom answer', UNUSED: 'unused answer' } }
} as const;

If you check the type of questionMap you'll see what I mean:如果您检查questionMap的类型,您会明白我的意思:

/* const questionMap: {
    readonly QUESTION_1: {
        readonly type: "boolean";
    };
    readonly QUESTION_2: {
        readonly type: "string";
    };
    readonly QUESTION_3: {
        readonly type: "dropdown";
        readonly options: {
            readonly CUSTOM: "custom answer";
            readonly UNUSED: "unused answer";
        };
    };
} */

So now the compiler knows enough about questionMap to process it.所以现在编译器对questionMap有足够的了解来处理它。 But what processing should be done?但是应该做什么处理呢?


I'm going to assume that you have some number of "simple" question types, where the question is specified as {type: "someString"} and the answer is a single type depending on "someString" .我将假设您有一些“简单”问题类型,其中问题被指定为{type: "someString"}并且答案是取决于"someString"的单一类型。 Let's tell the compiler about them:让我们告诉编译器它们:

type SimpleQuestionTypes = {
    boolean: boolean;
    string: string;
    number: number;
}

And then I'm going to say that a Question type is either one of those, or the "dropdown" type which needs to specify some options :然后我要说Question类型要么是其中之一,要么是需要指定一些options"dropdown"类型:

type Question = { type: keyof SimpleQuestionTypes } | { type: "dropdown", options: any };

This may not be strict enough for your use cases, and possibly you have other question types, but this should suffice for your example code at least.这对于您的用例可能不够严格,并且您可能还有其他问题类型,但这至少对于您的示例代码应该足够了。


Okay, now let's figure out how to take a type T extending Question and turn it into the expected answer type:好的,现在让我们弄清楚如何将类型T扩展Question并将其转换为预期的答案类型:

type Answer<T extends Question> =
    T['type'] extends keyof SimpleQuestionTypes ? SimpleQuestionTypes[T['type']] :
    'options' extends keyof T ? T['options'][keyof T['options']] :
    never;

If T has a type property corresponding to one of the simple question types, we will just read the answer type out of SimpleQuestionTypes .如果T具有对应于其中一种简单问题类型的type属性,我们将只从SimpleQuestionTypes中读取答案类型。 Otherwise, if there is an options property, we will grab a union of all the property value types of that options property.否则,如果有options属性,我们将获取该options属性的所有属性值类型的并集。

Finally, AnswerMap just maps that Answer computation over each property of an object:最后, AnswerMap只是将Answer计算映射到 object 的每个属性上:

type AnswerMap<T extends Record<keyof T, Question>> = {
    [K in keyof T]: Answer<T[K]>
} // extends infer O ? { [K in keyof O]: O[K] } : never;
// uncomment the above if you want to see easier-to-read output types

Now, the type of answerMap should be AnswerMap<typeof questionMap> , where we are using the TypeScript typeof type query operator to get that exact type of questionMap .现在, answerMap的类型应该是AnswerMap<typeof questionMap> ,我们在这里使用 TypeScript typeof类型查询运算符来获取确切类型的questionMap Like this:像这样:

const answerMap: AnswerMap<typeof questionMap> = {
    QUESTION_1: true,
    QUESTION_2: 'str',
    QUESTION_3: 'custom answer'
}

By the way, if we inspect this type, it is equivalent to the following:顺便说一句,如果我们检查这个类型,它相当于如下:

/* const answerMap: {
    readonly QUESTION_1: boolean;
    readonly QUESTION_2: string;
    readonly QUESTION_3: "custom answer" | "unused answer";
} */

which is presumably what you're going for.这大概就是你想要的。 Just to be sure, let's look at what fails when you give bad values for answerMap :为了确定起见,让我们看看当您为answerMap提供错误值时会发生什么失败:

const badAnswerMap1: AnswerMap<typeof questionMap> = { // error!
    QUESTION_1: true,
    QUESTION_2: 'str',
}; // Property 'QUESTION_3' is missing

const badAnswerMap2: AnswerMap<typeof questionMap> = {
    QUESTION_1: true,
    QUESTION_2: 'str',
    QUESTION_3: 'custom answer',
    QUESTION_4: 'str' // error! object literal may only specify known properties
}

const badAnswerMap3: AnswerMap<typeof questionMap> = {
    QUESTION_1: "oops", // error! string is not assignable to boolean
    QUESTION_2: 'str',
    QUESTION_3: 'custom answer',
}

const badAnswerMap4: AnswerMap<typeof questionMap> = {
    QUESTION_1: true,
    QUESTION_2: 'str',
    QUESTION_3: 'custom anwser', // error! "custom anwser" is not assignable to "custom answer" | "unused answer"
}

Looks right to me.在我看来是对的。 Hopefully that's enough to help you proceed with your actual use case.希望这足以帮助您继续进行实际用例。

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM