我正在处理一个大型 Sanity.io CMS 项目,该项目涉及编写大量 JS 来定义如下内容类型:
const person = {
type: 'document',
name: 'person',
fieldsets: [
{ name: 'personalDetails', title: 'Personal Details' },
],
fields: [
{ name: 'firstName', type: 'string', fieldset: 'personalDetails' },
{ name: 'surname', type: 'string', fieldset: 'personalDetails' },
],
}
我想知道是否可以创建一个 Document 接口/类型来推断所提供的 fieldsets
的名称,然后使用它们来限制对象的 fieldset
属性的类型在 fields
数组中到字符串文字的联合。这是一件小事,但它可以让我在制作这些内容类型的同时利用自动完成功能,从而加快开发速度。
我最初的尝试是这样的:
interface Document {
type: 'document';
name: string;
fieldsets: ReadonlyArray<{ name: string; title: string; }>;
fields: Array<{
name: string;
type: string;
fieldset: this['fieldset'][number]['name']; // Error: A 'this' type is available only in a non-static member of a class or interface
}>
}
我了解错误并尝试了各种解决方法,但如果不诉诸使用泛型类型并提供字段集名称数组,则无法实现我的目标:
interface Document<T extends ReadonlyArray<string>> {
/* ...rest of definition */
fieldset: ReadonlyArray<{ name: T[number] }>
fields: Array<{
name: string;
type: string;
fieldset: T[number];
}>
}
const person: Document<['personalDetails']> = {
/* works, but not exactly what I'm after */
}
那么 TypeScript 是否有可能在赋值期间动态推断该类型,或者我是否在寻找不存在的东西?
答案 0 :(得分:1)
您需要使用泛型来正确键入这是正确的。 Typescript 无法从常量推断泛型。它可以从函数调用中推断出泛型。所以你在这里要做的是定义一个标识函数作为创建类型安全文档的助手。
我希望通过使用类型 T 来描述整个 fieldsets
属性而不仅仅是名称来获得更好的推理。但是我仍然必须使用 as const
才能从对象属性中获取文字字符串值。
type FieldSet = {
name: string;
title: string;
}
type Field = {
name: string;
type: string;
fieldset: string;
}
interface MyDocument<T extends ReadonlyArray<FieldSet>> {
type: 'document';
name: string;
fieldsets: T;
fields: Array<Field & {fieldset: T[number]['name']}>
}
const createDoc = <T extends ReadonlyArray<FieldSet>>(schema: MyDocument<T>): MyDocument<T> => schema;
const person = createDoc({
type: 'document',
name: 'person',
fieldsets: [
{ name: 'personalDetails', title: 'Personal Details' }
] as const,
fields: [
{ name: 'firstName', type: 'string', fieldset: 'personalDetails' },
{ name: 'surname', type: 'string', fieldset: 'otherSet' }, // error
],
});
// note: we need `as const` because the string is an object property
const identity = <T extends any>(value: T): T => value;
const a = identity('personalDetails'); // this infers a literal
const b = identity({ name: 'personalDetails', title: 'Personal Details' }); // this does not