我在同一个项目中使用 TypeScript 和 Contentful,但我在思考如何以干净、合理和动态的方式在界面中映射 API 响应时遇到问题,因为来自 contentful 的 API 响应是巨大且丑陋 - 对于每种不同的内容类型,它们可能有所不同。
所以这些是我的主要问题:
- 实际上是在界面中映射 API 响应 - 它太大了,而且有很多不必要的数据,我需要将其全部放入界面中吗?
- 如果客户端稍后创建新的内容类型,那么接下来的 api 响应的结构会略有不同,此时接口(interface)会出现错误,我该怎么办?
感谢所有帮助,实际上只需要以正确的方式研究解决方案的建议。
这是内容丰富的 API 响应之一的示例:
{
"sys":{
"type":"Array"
},
"total":2,
"skip":0,
"limit":100,
"items":[
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"1w9AMDdCqRHRMEfzif3J0V",
"type":"Entry",
"createdAt":"2023-06-22T14:17:28.969Z",
"updatedAt":"2023-06-22T14:17:28.969Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"blogPost"
}
},
"locale":"en-US"
},
"fields":{
"title":"Second Test",
"body":{
"data":{
},
"content":[
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"This is a test.",
"nodeType":"text"
}
],
"nodeType":"paragraph"
}
],
"nodeType":"document"
},
"tags":[
{
"sys":{
"type":"Link",
"linkType":"Entry",
"id":"5vuJaOnrVvh34oAiBt8qgh"
}
},
{
"sys":{
"type":"Link",
"linkType":"Entry",
"id":"6feqnd9taR55ruluGBsp8h"
}
}
]
}
},
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"32eWu0P7zXgbxqum5nbdqP",
"type":"Entry",
"createdAt":"2023-06-22T14:14:34.053Z",
"updatedAt":"2023-06-22T14:14:34.053Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"blogPost"
}
},
"locale":"en-US"
},
"fields":{
"title":"Test",
"body":{
"data":{
},
"content":[
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"This is a test",
"nodeType":"text"
}
],
"nodeType":"heading-1"
},
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"",
"nodeType":"text"
}
],
"nodeType":"paragraph"
},
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"This is a test",
"nodeType":"text"
}
],
"nodeType":"heading-3"
},
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"",
"nodeType":"text"
}
],
"nodeType":"paragraph"
},
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"This is a test.",
"nodeType":"text"
}
],
"nodeType":"paragraph"
},
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"",
"nodeType":"text"
}
],
"nodeType":"paragraph"
}
],
"nodeType":"document"
},
"tags":[
{
"sys":{
"type":"Link",
"linkType":"Entry",
"id":"63QnQZsSKUUI3TNAvsI19J"
}
},
{
"sys":{
"type":"Link",
"linkType":"Entry",
"id":"1O7OQ1YFLMshW7BwBTL3ER"
}
}
],
"images":[
{
"sys":{
"type":"Link",
"linkType":"Asset",
"id":"35LgxsKv96DyW51Uu8DrnM"
}
},
{
"sys":{
"type":"Link",
"linkType":"Asset",
"id":"4bxRR1bBIknnVzRjeqvUIr"
}
}
]
}
}
],
"includes":{
"Entry":[
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"1O7OQ1YFLMshW7BwBTL3ER",
"type":"Entry",
"createdAt":"2023-06-22T14:13:23.918Z",
"updatedAt":"2023-06-22T14:13:23.918Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"countryTag"
}
},
"locale":"en-US"
},
"fields":{
"countryTag":"usa"
}
},
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"5vuJaOnrVvh34oAiBt8qgh",
"type":"Entry",
"createdAt":"2023-06-22T10:50:36.561Z",
"updatedAt":"2023-06-22T10:50:36.561Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"countryTag"
}
},
"locale":"en-US"
},
"fields":{
"countryTag":"australia"
}
},
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"63QnQZsSKUUI3TNAvsI19J",
"type":"Entry",
"createdAt":"2023-06-22T14:13:11.295Z",
"updatedAt":"2023-06-22T14:13:11.295Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"regionTag"
}
},
"locale":"en-US"
},
"fields":{
"title":"americas"
}
},
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"6feqnd9taR55ruluGBsp8h",
"type":"Entry",
"createdAt":"2023-06-22T10:41:10.693Z",
"updatedAt":"2023-06-22T10:41:10.693Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"regionTag"
}
},
"locale":"en-US"
},
"fields":{
"title":"apac"
}
}
],
"Asset":[
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"35LgxsKv96DyW51Uu8DrnM",
"type":"Asset",
"createdAt":"2023-06-22T14:14:11.979Z",
"updatedAt":"2023-06-22T14:14:11.979Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"locale":"en-US"
},
"fields":{
"title":"Test1",
"description":"",
"file":{
"url":"//images.ctfassets.net/rikydtrxnc79/35LgxsKv96DyW51Uu8DrnM/d8a11b6dc57a416a143af10b3569a7e0/pexels-photo-3844788.jpeg",
"details":{
"size":83381,
"image":{
"width":500,
"height":667
}
},
"fileName":"pexels-photo-3844788.jpeg",
"contentType":"image/jpeg"
}
}
},
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"4bxRR1bBIknnVzRjeqvUIr",
"type":"Asset",
"createdAt":"2023-06-22T14:14:26.979Z",
"updatedAt":"2023-06-22T14:14:26.979Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"locale":"en-US"
},
"fields":{
"title":"Test2",
"description":"",
"file":{
"url":"//images.ctfassets.net/rikydtrxnc79/4bxRR1bBIknnVzRjeqvUIr/4ffca3cdb4db84b6ee2129cf05f06cdc/premium_photo-1661964088064-dd92eaaa7dcf.jpeg",
"details":{
"size":46936,
"image":{
"width":1000,
"height":714
}
},
"fileName":"premium_photo-1661964088064-dd92eaaa7dcf.jpeg",
"contentType":"image/jpeg"
}
}
}
]
}
}
现在,项目数组和包含数组在对象中可能有不同的数据集,因此这就是为什么我发现很难思考如何解决新内容类型和现有接口(interface)/模式不适用于它们的问题.
我已经制定了这样的响应,但认为它太过分了?
export interface ContentfulApiResponse {
sys: {
type: string;
};
total: number;
skip: number;
limit: number;
items: [
{
metadata: {
tags: [];
};
sys: {
space: {
sys: {
type: string;
linkType: string;
id: string;
};
};
id: string;
type: string;
createdAt: string;
updatedAt: string;
environment: {
sys: {
id: string;
type: string;
linkType: string;
};
};
revision: number;
contentType: {
sys: {
type: string;
linkType: string;
id: string;
};
};
locale: string;
};
fields: {
title: string;
body: {
data: {};
content: [
{
data: {};
content: [
{
data: {};
marks: [];
value: string;
nodeType: string;
}
];
nodeType: string;
}
];
nodeType: string;
};
tags: [
{
sys: {
type: string;
linkType: string;
id: string;
};
},
{
sys: {
type: string;
linkType: string;
id: string;
};
}
];
};
},
{
metadata: {
tags: [];
};
sys: {
space: {
sys: {
type: string;
linkType: string;
id: string;
};
};
id: string;
type: string;
createdAt: string;
updatedAt: string;
environment: {
sys: {
id: string;
type: string;
linkType: string;
};
};
revision: number;
contentType: {
sys: {
type: string;
linkType: string;
id: string;
};
};
locale: string;
};
fields: {
title: string;
body: {
data: {};
content: [
{
data: {};
content: [
{
data: {};
marks: [];
value: string;
nodeType: string;
}
];
nodeType: string;
},
{
data: {};
content: [
{
data: {};
marks: [];
value: string;
nodeType: string;
}
];
nodeType: string;
},
{
data: {};
content: [
{
data: {};
marks: [];
value: string;
nodeType: string;
}
];
nodeType: string;
},
{
data: {};
content: [
{
data: {};
marks: [];
value: "";
nodeType: string;
}
];
nodeType: string;
},
{
data: {};
content: [
{
data: {};
marks: [];
value: string;
nodeType: string;
}
];
nodeType: string;
},
{
data: {};
content: [
{
data: {};
marks: [];
value: string;
nodeType: string;
}
];
nodeType: string;
}
];
nodeType: string;
};
tags: [
{
sys: {
type: string;
linkType: string;
id: string;
};
},
{
sys: {
type: string;
linkType: string;
id: string;
};
}
];
images: [
{
sys: {
type: string;
linkType: string;
id: string;
};
},
{
sys: {
type: string;
linkType: string;
id: string;
};
}
];
};
}
];
includes: {
Entry: ContentfulIncludesEntry[];
Asset: ContentfulIncludesAsset[];
};
}
interface ContentfulIncludesAsset {
metadata: {
tags: [];
};
sys: {
space: {
sys: {
type: string;
linkType: string;
id: string;
};
};
id: string;
type: string;
createdAt: string;
updatedAt: string;
environment: {
sys: {
id: string;
type: string;
linkType: string;
};
};
revision: number;
locale: string;
};
fields: {
title: string;
description: string;
file: {
url: string;
details: {
size: number;
image: {
width: number;
height: number;
};
};
fileName: string;
contentType: string;
};
};
}
interface ContentfulIncludesEntry {
metadata: {
tags: [];
};
sys: {
space: {
sys: {
type: string;
linkType: string;
id: string;
};
};
id: string;
type: string;
createdAt: string;
updatedAt: string;
environment: {
sys: {
id: string;
type: string;
linkType: string;
};
};
revision: number;
contentType: {
sys: {
type: string;
linkType: string;
id: string;
};
};
locale: string;
};
fields: {
countryTag?: string;
regionTag?: string;
};
}
请记住,字段对象可能会随每种内容类型而变化。
最佳答案
我的第一个想法是,您应该首先为每个属性名称创建一个通用类型。系统、元数据、标签、环境等,完全忽略它们将具有循环引用的事实,这可能会阻止它们编译,因为在这个阶段您只想将其全部记录下来并理解它。例如:
interface Item {
metadata?: Metadata;
sys?: Sys;
fields?: Fields;
}
另一个想法是甚至不要尝试强类型化服务器响应。相反,接受响应未知
,然后使用 zod将数据断言为您的代码期望的类型。这样做的一个优点是,运行时断言将帮助您以单元测试和静态类型检查无法做到的方式发现错误和意外。
但是,您确定需要自己执行这些操作吗?如果类似 @bgschiller/contentful-typescript-codegen没有帮助,我希望你能在那里找到你可以使用的东西?
(注意:我不推荐 @bgschiller/contentful-typescript-codegen。我以前从未听说过它,只是通过在 npmjs.com 上搜索“types/contentful”找到它。)
关于typescript - 如何使用接口(interface)使 API 响应类型安全/声明? typescript ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76550838/