我想使用模板文字类型来表示通配符
I want to use Template literal types to represent wildcards
我正在为我的业务在打字稿中输入现有的 javascript 文件。
这是一个示例对象:
[
{
// The column names givenName, familyName, and picture are ones of examples.
"givenName": {
"text": "Foo",
"type": "text"
},
"familyName": {
"text": "Bar",
"type": "text"
},
"picture": {
"text": "abc.png",
"type": "image",
"thumbnail": "https://example.com/thumbnail/sample.png"
},
// The following two properties are paths to the PDF and thumbnail generated from the above information.
"pdf62882329b9baf800217efe7c": "https://example.com/pdf/genarated_pdf.pdf",
"thumbnail62882329b9baf800217efe7c": [
"https://example.com/thumbnail/head.png",
"https://example.com/thumbnail/tail.png"
]
},
{
// ... (The structure is the same as above object.)
}, // ...
]
我想输入对象部分如下:
type Row = {
[headerKey: string]: {
text: string;
type: "text";
} | {
text: string;
type: "image";
thumbnail: string;
};
// The following two properties are the paths to the generated PDF and thumbnails.
// The reason for concatenating the id is to avoid name conflicts when the column names "pdf" and "thumbnail" come in the column name.
// (Since the string for id is randomly generated, I believe it is unlikely that this string will be used for the column name.)
pdf+id: string; // path to the generated PDF
thumbnail+id: [string, string]; // path to the generated thumbnail (It have two elements because the image has two sides.)
};
并且我使用了模板字面量类型,类型如下:
type Row = {
[headerKey: string]: {
text: string;
type: "text";
} | {
text: string;
type: "image";
thumbnail: string;
};
[pdfKey: `pdf${string}`]: string;
[thumbnailKey: `thumbnail${string}`]: [string, string];
};
但它没有按预期工作。
有什么方法可以成功键入此对象?
我也认为不可能将此逻辑放在 TypeScript 中的单个类型中。但是在使用泛型函数时仍然可以验证这样的结构。
当我们将对象传递给泛型函数时,我们可以使用泛型类型来验证对象的类型。
function createRow<T extends ValidateRow<T>>(row: T): T {
return row
}
现在我们只需要泛型。
type ValidateRow<T> = {
[K in keyof T]: K extends `pdf${string}`
? string
: K extends `thumbnail${string}`
? readonly [string, string]
: {
readonly text: string;
readonly type: "text";
} | {
text: string;
type: "image";
thumbnail: string;
}
}
如您所见,此类型遵循简单的 if/else 逻辑来确定每个 属性 名称的正确类型。
现在让我们用一个有效的对象来测试它:
createRow({
"givenName": {
"text": "Foo",
"type": "text"
},
"familyName": {
"text": "Bar",
"type": "text"
},
"picture": {
"text": "abc.png",
"type": "image",
"thumbnail": "https://example.com/thumbnail/sample.png"
},
"pdf62882329b9baf800217efe7c": "https://example.com/pdf/genarated_pdf.pdf",
"thumbnail62882329b9baf800217efe7c": [
"https://example.com/thumbnail/head.png",
"https://example.com/thumbnail/rail.png"
]
})
// No error!
这通过了检查。让我们尝试一些错误:
createRow({
"givenName": {
"text": "Foo",
"type": "text"
},
"familyName": {
"text": "Bar",
"type": "text"
},
"picture": {
"text": "abc.png",
"type": "image",
"thumbnail": "https://example.com/thumbnail/sample.png"
},
"pdf62882329b9baf800217efe7c": "https://example.com/pdf/genarated_pdf.pdf",
"thumbnail62882329b9baf800217efe7c": [
"https://example.com/thumbnail/head.png"
]
})
// Error: Type '[string]' is not assignable to type 'readonly [string, string]'
createRow({
"givenName": {
"text": "Foo",
"type": "text"
},
"familyName": {
"text": "Bar",
"type": "text2"
},
"picture": {
"text": "abc.png",
"type": "image",
"thumbnail": "https://example.com/thumbnail/sample.png"
},
"pdf62882329b9baf800217efe7c": "https://example.com/pdf/genarated_pdf.pdf",
"thumbnail62882329b9baf800217efe7c": [
"https://example.com/thumbnail/head.png",
"https://example.com/thumbnail/rail.png"
]
})
// Error: Type '"text2"' is not assignable to type '"text" | "image"'. Did you mean '"text"'
因此,只要我们使用一些通用函数,即使逻辑复杂,我们也可以验证对象。
如果您不介意尽早声明对象的所有键,您可以将 Row
类型定义为:
type Name = {
text: string;
type: "text";
};
type RowShape = {
thumbnail: [string, string];
pdf: string;
givenName: Name;
familyName: Name;
picture: {
text: string;
type: "image";
thumbnail: string;
};
}
type Row = {
[x in keyof RowShape
as `${x extends 'pdf' ?
`pdf${string}`: x extends 'thumbnail'?
`thumbnail${string}`: x}`
]: RowShape[x]
}
我正在为我的业务在打字稿中输入现有的 javascript 文件。
这是一个示例对象:
[
{
// The column names givenName, familyName, and picture are ones of examples.
"givenName": {
"text": "Foo",
"type": "text"
},
"familyName": {
"text": "Bar",
"type": "text"
},
"picture": {
"text": "abc.png",
"type": "image",
"thumbnail": "https://example.com/thumbnail/sample.png"
},
// The following two properties are paths to the PDF and thumbnail generated from the above information.
"pdf62882329b9baf800217efe7c": "https://example.com/pdf/genarated_pdf.pdf",
"thumbnail62882329b9baf800217efe7c": [
"https://example.com/thumbnail/head.png",
"https://example.com/thumbnail/tail.png"
]
},
{
// ... (The structure is the same as above object.)
}, // ...
]
我想输入对象部分如下:
type Row = {
[headerKey: string]: {
text: string;
type: "text";
} | {
text: string;
type: "image";
thumbnail: string;
};
// The following two properties are the paths to the generated PDF and thumbnails.
// The reason for concatenating the id is to avoid name conflicts when the column names "pdf" and "thumbnail" come in the column name.
// (Since the string for id is randomly generated, I believe it is unlikely that this string will be used for the column name.)
pdf+id: string; // path to the generated PDF
thumbnail+id: [string, string]; // path to the generated thumbnail (It have two elements because the image has two sides.)
};
并且我使用了模板字面量类型,类型如下:
type Row = {
[headerKey: string]: {
text: string;
type: "text";
} | {
text: string;
type: "image";
thumbnail: string;
};
[pdfKey: `pdf${string}`]: string;
[thumbnailKey: `thumbnail${string}`]: [string, string];
};
但它没有按预期工作。 有什么方法可以成功键入此对象?
我也认为不可能将此逻辑放在 TypeScript 中的单个类型中。但是在使用泛型函数时仍然可以验证这样的结构。
当我们将对象传递给泛型函数时,我们可以使用泛型类型来验证对象的类型。
function createRow<T extends ValidateRow<T>>(row: T): T {
return row
}
现在我们只需要泛型。
type ValidateRow<T> = {
[K in keyof T]: K extends `pdf${string}`
? string
: K extends `thumbnail${string}`
? readonly [string, string]
: {
readonly text: string;
readonly type: "text";
} | {
text: string;
type: "image";
thumbnail: string;
}
}
如您所见,此类型遵循简单的 if/else 逻辑来确定每个 属性 名称的正确类型。
现在让我们用一个有效的对象来测试它:
createRow({
"givenName": {
"text": "Foo",
"type": "text"
},
"familyName": {
"text": "Bar",
"type": "text"
},
"picture": {
"text": "abc.png",
"type": "image",
"thumbnail": "https://example.com/thumbnail/sample.png"
},
"pdf62882329b9baf800217efe7c": "https://example.com/pdf/genarated_pdf.pdf",
"thumbnail62882329b9baf800217efe7c": [
"https://example.com/thumbnail/head.png",
"https://example.com/thumbnail/rail.png"
]
})
// No error!
这通过了检查。让我们尝试一些错误:
createRow({
"givenName": {
"text": "Foo",
"type": "text"
},
"familyName": {
"text": "Bar",
"type": "text"
},
"picture": {
"text": "abc.png",
"type": "image",
"thumbnail": "https://example.com/thumbnail/sample.png"
},
"pdf62882329b9baf800217efe7c": "https://example.com/pdf/genarated_pdf.pdf",
"thumbnail62882329b9baf800217efe7c": [
"https://example.com/thumbnail/head.png"
]
})
// Error: Type '[string]' is not assignable to type 'readonly [string, string]'
createRow({
"givenName": {
"text": "Foo",
"type": "text"
},
"familyName": {
"text": "Bar",
"type": "text2"
},
"picture": {
"text": "abc.png",
"type": "image",
"thumbnail": "https://example.com/thumbnail/sample.png"
},
"pdf62882329b9baf800217efe7c": "https://example.com/pdf/genarated_pdf.pdf",
"thumbnail62882329b9baf800217efe7c": [
"https://example.com/thumbnail/head.png",
"https://example.com/thumbnail/rail.png"
]
})
// Error: Type '"text2"' is not assignable to type '"text" | "image"'. Did you mean '"text"'
因此,只要我们使用一些通用函数,即使逻辑复杂,我们也可以验证对象。
如果您不介意尽早声明对象的所有键,您可以将 Row
类型定义为:
type Name = {
text: string;
type: "text";
};
type RowShape = {
thumbnail: [string, string];
pdf: string;
givenName: Name;
familyName: Name;
picture: {
text: string;
type: "image";
thumbnail: string;
};
}
type Row = {
[x in keyof RowShape
as `${x extends 'pdf' ?
`pdf${string}`: x extends 'thumbnail'?
`thumbnail${string}`: x}`
]: RowShape[x]
}