我将假定propertyType
属性将是描述类型的字符串,如"boolean"
或"date"
.如果是这样的话,您需要保留一个从这些字符串映射到它们所代表的类型的类型:
interface TypeMap {
boolean: boolean;
number: number;
string: string;
date: Date;
// add any name-to-type mappings you need here
}
然后我们可以说,您的SurveyQuestion
类型需要使用TypeMap
中的一个键作为propertyType
:
interface SurveyQuestion {
propertyName: string,
propertyType: keyof TypeMap,
}
当你定义question
的时候,你不会想把它定义为SurveyQuestion[]
.您需要编译器跟踪初始化数组文字中的特定propertyName
和propertyType
值,但是如果您将变量属性注释为SurveyQuestion[]
,编译器将忘记所有这些细节.所以你想写const questions = ...
,而不是const questions: SurveyQuestion[] = ...
.
即使这样还不够;除非您告诉编译器,否则编译器不会意识到您关心这些属性值中的literal types个.通常,即使没有注释,像[{a: "abc", x: "xyz"}, {a: "def", x: "uvw"}]
这样的值也会被推断为类型Array<{a: string, x: string}>
.人们想要这样的类型更常见,而不是像"abc"
和"xyz"
这样非常具体的类型.但你想要尽可能具体的类型,所以你可以用const
assertion来实现:
const questions = [
{ propertyName: "isNewUser", propertyType: "boolean" },
{ propertyName: "satisfactionScore", propertyType: "number" },
{ propretyName: "comments", propertyType: "string" },
] as const;
而IntelliSense向您展示了questions
的类型:
/* const questions: readonly [{
readonly propertyName: "isNewUser";
readonly propertyType: "boolean";
}, {
readonly propertyName: "satisfactionScore";
readonly propertyType: "number";
}, {
readonly propretyName: "comments";
readonly propertyType: "string";
}] */
这就是足够的信息了,万岁!
但请注意,我们根本没有使用SurveyQuestion
.编译器没有意识到我们需要SurveyQuestion
,所以它没有发现我写的是propretyName
,而不是propertyName
.哎呀!
要解决这个问题,可以使用新的satisfies
operator让编译器判断初始值设定项是不是由SurveyQuestion
组成的数组,而不会将变量扩展到该类型:
const questions = [
{ propertyName: "isNewUser", propertyType: "boolean" },
{ propertyName: "satisfactionScore", propertyType: "number" },
{ propretyName: "comments", propertyType: "string" }, // error!
// ~~~~~~~~~~~~~~~~~~~~~~~ <-- Did you mean to write 'propertyName'?
] as const satisfies ReadonlyArray<SurveyQuestion>;
现在编译器已经捕获了错误,我们可以修复它:
const questions = [
{ propertyName: "isNewUser", propertyType: "boolean" },
{ propertyName: "satisfactionScore", propertyType: "number" },
{ propertyName: "comments", propertyType: "string" },
// { propertyName: "anotherProp", propertyType: "date" }
] as const satisfies ReadonlyArray<SurveyQuestion>;
好的,太好了.
现在我们要做的就是用questions
来定义SurveyWithQuestions
.首先,我们可以使用the TypeScript typeof
type operator获取questions
的类型,并使用number
对其进行索引以获得union,如果它的元素类型:
type QuestionType = typeof questions[number];
/* type QuestionType = {
readonly propertyName: "isNewUser";
readonly propertyType: "boolean";
} | {
readonly propertyName: "satisfactionScore";
readonly propertyType: "number";
} | {
readonly propertyName: "comments";
readonly propertyType: "string";
} */
现在我们可以iterate over that union and remap it to object properties:
type ResponseFormat = {
[T in QuestionType as T["propertyName"]]:
TypeMap[T["propertyType"]]
}
/* type ResponseFormat = {
isNewUser: boolean;
satisfactionScore: number;
comments: string;
} */
最后,我们可以使用任何其他"静态"属性对其进行扩展,以获得SurveyQuestions
:
interface SurveyWithQuestions extends ResponseFormat {
date: Date;
username: string;
}
让我们来测试一下:
let survey1: SurveyWithQuestions = {
date: new Date("1/1/2023"),
username: "johndoe",
isNewUser: true,
satisfactionScore: 5,
comments: "happy user",
}; // okay
因此,这起到了预期的作用.让我们看看如果在我们的questions
定义中添加一行会发生什么:
const questions = [
{ propertyName: "isNewUser", propertyType: "boolean" },
{ propertyName: "satisfactionScore", propertyType: "number" },
{ propertyName: "comments", propertyType: "string" },
{ propertyName: "dateOfBirth", propertyType: "date" } // <-- add this
] as const satisfies ReadonlyArray<SurveyQuestion>;
let survey1: SurveyWithQuestions = { // error!
date: new Date("1/1/2023"),
username: "johndoe",
isNewUser: true,
satisfactionScore: 5,
comments: "happy user",
};
//类型中缺少属性""ateOfBirth""...
//但在‘SurveyWithQuestions’类型中是必需的.
因此,编译器现在希望存在一个dateOfBirth
属性.让我们给它一个:
let survey1: SurveyWithQuestions = {
date: new Date("1/1/2023"),
username: "johndoe",
isNewUser: true,
satisfactionScore: 5,
comments: "happy user",
dateOfBirth: "yesterday" // error!
// Type 'string' is not assignable to type 'Date'.
};
哦,错误的类型:
let survey1: SurveyWithQuestions = {
date: new Date("1/1/2023"),
username: "johndoe",
isNewUser: true,
satisfactionScore: 5,
comments: "happy user",
dateOfBirth: new Date(Date.now() - 8.64e7)
}; // okay
看上go 不错!
Playground link to code个