我想动态扩展一个类型,以便我可以执行以下操作:
interface SurveyQuestion {
propertyName: string,
propertyType: Object,
}
const questions: SurveyQuestion[] = [
{ propertyName: "isNewUser", propertyType: typeof Boolean },
{ propertyName: "satisfactionScore", propertyType: typeof Number },
{ propertyName: "comments", propertyType: typeof String },
];
type SurveyWithQuestions = {
date: Date;
username: string;
}
let survey1: SurveyWithQuestions = {
date: new Date("1/1/2023"),
username: "johndoe",
isNewUser: true,
satisfactionScore: 5,
comments: "happy user"
};
...这样添加的新问题就不会出错,并且允许完成代码。 This使用泛型和对象键来接近。但我无法正确修改它来迭代问题。
最佳答案
我假设 propertyType
property 将是描述类型的字符串,例如 "boolean"
或"date"
。如果是这样,那么为了使其工作,您需要保留一个从这些字符串映射到它们代表的 TypeScript 类型的类型:
interface TypeMap {
boolean: boolean;
number: number;
string: string;
date: Date;
// add any name-to-type mappings you need here
}
然后我们可以说你的 SurveyQuestion
type 需要使用 TypeMap
中的一个键如propertyType
:
interface SurveyQuestion {
propertyName: string,
propertyType: keyof TypeMap,
}
当您定义question
时,你不想annotate其为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
的数组s 而不将变量扩展为该类型:
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>;
好的,太好了。
现在我们要做的就是定义 SurveyWithQuestions
就questions
而言。首先我们可以得到questions
的类型使用the TypeScript typeof
type operator ,并用 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",
};
//类型中缺少属性“dateOfBirth”... //但在“SurveyWithQuestions”类型中是必需的。
所以编译器现在期望 dateOfBirth
属性(property)的存在。让我们举一个:
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
看起来不错!
关于typescript - 动态 Typescript 对象属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75707569/