如何使用接口使API响应类型安全/声明?TypeScript

wvt8vs2t  于 2023-06-30  发布在  TypeScript
关注(0)|答案(1)|浏览(144)

我在同一个项目中使用TypeScript和Contentful,并且在如何以一种干净,明智和动态的方式在接口中MapAPI响应方面遇到了问题,因为Contentful的API响应是巨大而丑陋的-对于每种不同的内容类型,它们可能是不同的。
这些是我的主要问题:
1.实际上在一个接口中Map出API响应--它如此之大,有如此多不必要的数据,我需要把它们都放在一个接口中吗?
1.如果客户端后来创建了新的内容类型,那么接下来的API响应的结构会略有不同,此时接口会出错,我该怎么办?
感谢所有的帮助,真的只是需要建议下来的正确方式来调查的解决方案。
这是一个内容丰富的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"
               }
            }
         }
      ]
   }
}

现在items数组和includes数组在对象中可能有不同的数据集,所以这就是为什么我很难理解如何解决新内容类型和现有接口/模式不适用的问题。
我已经画出了这样的React,但认为它过度?

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;
  };
}

请记住,fields对象可以随每种内容类型而变化。

zqry0prt

zqry0prt1#

我的第一个想法是,您应该从为每个属性名创建一个公共类型开始。Sys、Metadata、Tag、Environment等,完全忽略了它们会有循环引用的事实,这可能会阻止它们编译,因为在这个阶段,你只想把它都弄下来并理解它。例如:

interface Item {
  metadata?: Metadata;
  sys?: Sys;
  fields?: Fields;
}

另一个想法是甚至不尝试强类型的服务器响应。相反,接受响应是unknown,然后使用zod将数据Assert为代码所需的类型。这样做的一个优点是,运行时Assert将以单元测试和静态类型检查根本无法做到的方式帮助您发现错误和意外情况。
但是,你确定你需要自己做这些事情吗?如果像@bgschiller/contentful-typescript-codegen这样的东西没有帮助,我希望你能找到一些你可以在那里使用的东西?
(Note:我不推荐使用@bgschiller/contentful-typescript-codegen。我以前从未听说过它,只是通过在npmjs.com上搜索“types/contentful”才找到它。

相关问题