[英]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
.该示例用于定义
QuestionMap
和AnswerMap
。 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.
希望这足以帮助您继续进行实际用例。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.