JSON 转 TypeScript 类型定义指南 – wiki基地


JSON 转 TypeScript 类型定义指南:构建健壮前端应用的基石

在现代 Web 开发中,JSON(JavaScript Object Notation)作为数据交换的通用格式,无处不在。无论是从后端 API 获取数据,读取配置文件,还是处理客户端存储,我们几乎时刻都在与 JSON 打交道。与此同时,TypeScript 作为 JavaScript 的超集,凭借其强大的静态类型系统,为构建大型、可维护的应用提供了坚实的基础。

然而,JSON 的动态性(无固定模式)与 TypeScript 的静态性(要求明确类型)之间存在天然的冲突。当我们在 TypeScript 项目中处理 JSON 数据时,如果不为这些数据定义明确的类型,我们将失去 TypeScript 带来的诸多优势:静态检查、代码提示、重构便利性等。

因此,将 JSON 数据结构“翻译”成 TypeScript 的类型定义(如 interfacetype)成为了前端开发中的一项核心任务。本文将深入探讨为什么需要进行这种转换,如何手动完成,如何利用自动化工具提效,以及处理各种复杂 JSON 结构的技巧和最佳实践。

第一部分:为何需要将 JSON 转换为 TypeScript 类型?

JSON 本质上是一个轻量级的数据交换格式,它的设计目标是易于人阅读和编写,同时也易于机器解析和生成。它基于 JavaScript 的一个子集,但它本身并没有内置的类型定义或模式强制机制(虽然有 JSON Schema,但并非 JSON 本身的组成部分)。这意味着一个 JSON 对象可以在运行时拥有任意的属性,属性的值可以是任意合法的 JSON 类型(字符串、数字、布尔值、数组、对象、null)。

而 TypeScript 的核心价值在于引入了静态类型。在编译时,TypeScript 编译器会检查代码中的类型是否匹配,从而捕获潜在的错误。当你处理一个变量时,如果你知道它的确切类型(例如 User | nullProduct[]),IDE 就能为你提供准确的代码补全,编译器能检查你是否访问了不存在的属性或调用了不匹配的方法。

将 JSON 转换为 TypeScript 类型定义,就是在为那些“无类型”的 JSON 数据赋予结构和契约。这样做带来了以下核心优势:

  1. 增强代码的可读性和可维护性: 类型定义清晰地文档化了数据结构。其他开发者(或未来的你)看到类型定义就能快速理解数据的形状,无需去猜测或翻阅 API 文档。
  2. 提供强大的静态检查: 在编译阶段就能发现因数据结构不匹配导致的错误。例如,如果你尝试访问一个可能不存在的属性,或者将错误类型的值赋给一个变量,TypeScript 编译器会立即报错。这比在运行时才发现错误要高效得多。
  3. 改善开发体验: 借助类型信息,IDE 可以提供精确的代码自动补全、参数提示和错误提示。这极大地提高了开发效率,减少了拼写错误和低级错误。
  4. 提高代码的健壮性: 明确的类型契约减少了运行时因数据格式不符合预期而引发的错误。即使后端 API 返回的数据与预期不符,类型系统也能在集成阶段就发出警告(虽然这更多依赖于运行时的数据验证结合类型定义,但类型定义是基础)。
  5. 简化重构: 如果后端 API 的数据结构发生变化,你可以先更新相应的 TypeScript 类型定义。然后,编译器会立刻指出代码中所有使用到旧结构的箇所,帮助你快速、安全地进行重构。

简而言之,为 JSON 数据定义 TypeScript 类型,就是将运行时可能出现的类型问题前置到编译时解决,用类型安全替换运行时不确定性。

第二部分:手动转换 JSON 到 TypeScript 类型

理解手动转换的过程是至关重要的,即使你最终会依赖自动化工具。手动转换能帮助你深入理解 JSON 结构与 TypeScript 类型之间的对应关系,以及如何处理各种特殊情况。

假设我们从一个 API 接口获取到如下 JSON 数据:

json
{
"id": 101,
"name": "Alice",
"isActive": true,
"roles": ["admin", "editor"],
"address": {
"street": "123 Main St",
"city": "Anytown",
"zipCode": "12345",
"country": "USA"
},
"profile": null,
"lastLogin": "2023-10-27T10:00:00Z"
}

现在,我们一步步将它转换为 TypeScript 类型定义。通常,我们会使用 interfacetype 关键字来定义对象的结构。interface 更常用于描述对象的形状,而 type 可以用于创建类型别名、联合类型、交叉类型等,功能更灵活。在本例中,interface 是一个不错的选择。

我们将定义一个名为 User 的 interface 来表示这个 JSON 对象的根结构。

1. 识别顶级属性及基础类型

遍历 JSON 对象的顶级属性:

  • "id": 值是 101 (数字) -> TypeScript 类型 number
  • "name": 值是 "Alice" (字符串) -> TypeScript 类型 string
  • "isActive": 值是 true (布尔值) -> TypeScript 类型 boolean
  • "roles": 值是 ["admin", "editor"] (字符串数组) -> TypeScript 类型 string[]Array<string>
  • "address": 值是一个 {} (对象) -> 这需要定义一个嵌套的类型,稍后处理。
  • "profile": 值是 null -> TypeScript 类型 null. 由于 profile 属性本身可能存在也可能不存在(虽然示例中存在但值为 null),或者它可能有其他非 null 值,我们需要考虑其完整类型。如果 profile 理论上可以是 null 或者一个某种结构的对象,它的类型可能是 ProfileType | null. 在这个特定示例中,我们只看到 null,但为了实用性,我们通常会根据 API 文档或更多示例来判断它可能是什么类型。暂时我们只定义它为 null
  • "lastLogin": 值是 "2023-10-27T10:00:00Z" (字符串) -> TypeScript 类型 string. 虽然它代表一个日期时间,JSON 标准中没有日期类型,通常用字符串表示。如果你后续会将它解析为 JavaScript 的 Date 对象,那么在使用时需要注意转换,但类型定义本身应反映 JSON 中的原始类型。

基于此,我们可以开始构建 User interface:

typescript
interface User {
id: number;
name: string;
isActive: boolean;
roles: string[]; // 或者 Array<string>
// address 和 profile 稍后处理
lastLogin: string;
}

2. 处理嵌套对象

JSON 中的 "address" 属性的值是一个嵌套对象:

json
{
"street": "123 Main St",
"city": "Anytown",
"zipCode": "12345",
"country": "USA"
}

我们需要为其定义一个新的 interface,例如 Address。遍历其属性:

  • "street": "123 Main St" (字符串) -> string
  • "city": "Anytown" (字符串) -> string
  • "zipCode": "12345" (字符串) -> string
  • "country": "USA" (字符串) -> string

定义 Address interface:

typescript
interface Address {
street: string;
city: string;
zipCode: string;
country: string;
}

然后,在 User interface 中引用 Address 类型:

“`typescript
interface User {
id: number;
name: string;
isActive: boolean;
roles: string[];
address: Address; // 使用上面定义的 Address 类型
// profile 稍后处理
lastLogin: string;
}

// interface Address { … } // Address 定义放在 User 定义之前或之后都可以,只要在同一个作用域
“`

3. 处理 null 和可选属性

在我们的示例中,"profile" 的值是 null。这意味着这个属性是存在的,但它的值是 null。在 TypeScript 中,如果一个属性可能接收 null 值,你需要使用联合类型 TypeOfProperty | null

此外,考虑如果某个属性在某些情况下可能完全不存在于 JSON 对象中(即,属性名本身可能缺失),这称为可选属性。在 TypeScript 中,使用属性名后的 ? 标记来表示可选性。

假设根据 API 文档,profile 属性可能不存在,或者即使存在,它的值也可能是 null,或者是一个 ProfileData 类型的对象。我们可以定义一个 ProfileData interface,然后将 profile 的类型定义为 ProfileData | null | undefined 或更简洁地使用可选属性 profile?: ProfileData | null;。如果属性是可选的 (?),TypeScript 会自动将其类型隐含地加上 | undefined。所以 profile?: ProfileData | null; 实际上等同于 profile: ProfileData | null | undefined;

为了简单起见,假设 profile 属性总是存在,但值可能是 null 或一个 { "bio": string, "website": string } 结构的对象。我们先定义 ProfileData

typescript
interface ProfileData {
bio: string;
website: string;
}

然后更新 User interface 中的 profile 属性。根据示例,它目前是 null。如果它 只可能null 或者一个 ProfileData 对象,则类型是 ProfileData | null。如果它 还可能不存在,则是 ProfileData | null | undefined (或 profile?: ProfileData | null).

假设根据 API 实际情况,profile 属性 总是存在,但值可能是 ProfileData 对象或 null

“`typescript
interface User {
id: number;
name: string;
isActive: boolean;
roles: string[];
address: Address;
profile: ProfileData | null; // 属性存在,值可能是 ProfileData 或 null
lastLogin: string;
}

interface Address {
street: string;
city: string;
zipCode: string;
country: string;
}

interface ProfileData {
bio: string;
website: string;
}
“`

如果 profile 属性 可能不存在,且如果存在值可能是 ProfileData 对象或 null

“`typescript
interface User {
id: number;
name: string;
isActive: boolean;
roles: string[];
address: Address;
profile?: ProfileData | null; // 属性可选,如果存在值可能是 ProfileData 或 null
lastLogin: string;
}

// … Address 和 ProfileData 定义不变
“`
选择哪种方式取决于真实的 JSON 数据源的行为。仔细阅读 API 文档或检查多个 JSON 响应示例是关键。

4. 处理数组中的元素类型

示例中的 "roles" 属性是一个字符串数组 (["admin", "editor"])。我们已经将其定义为 string[]

如果数组包含不同类型的元素,例如 [1, "two", true],可以使用联合类型 (number | string | boolean)[]

如果数组包含对象,例如用户列表 [...],那么需要为数组中的每个对象定义一个类型,例如 User[]Array<User>,其中 User 是前面定义的用户对象类型。

5. 总结手动转换步骤

  1. 从最外层的 JSON 对象开始。
  2. 为对象定义一个 interface 或 type。
  3. 遍历对象的每个属性:
    • 确定属性名。
    • 确定属性值的 JSON 类型(string, number, boolean, object, array, null)。
    • 根据 JSON 类型,确定对应的 TypeScript 类型。
      • 基础类型 (string, number, boolean) -> 对应的 TS 类型。
      • null -> TS 类型 null。通常与实际类型组成联合类型(Type | null)。
      • 对象 {} -> 定义一个新的 interface 或 type 来描述该对象的结构,并在当前类型中引用它。
      • 数组 [] -> 确定数组中元素的类型。如果是单一类型,使用 ElementType[]Array<ElementType>。如果包含不同类型,使用联合类型 (TypeA | TypeB)[]。如果包含对象,定义对象类型后使用 ObjectType[]
    • 考虑属性是否可选 (?)。
    • 考虑属性值是否可能为 null (| null)。
  4. 处理所有嵌套的对象和数组元素,直到所有部分都有了对应的类型定义。
  5. 为所有定义的 interface 或 type 选择清晰、描述性的名称。

手动转换对于理解过程、处理简单结构或微调工具生成的类型非常有用。然而,对于大型、复杂的 JSON 结构,手动转换将变得耗时且容易出错。

第三部分:自动化工具:提高效率和准确性

幸运的是,社区提供了许多强大的自动化工具,可以根据提供的 JSON 样本快速生成 TypeScript 类型定义。这些工具解析 JSON 结构,推断出属性类型、可选性以及嵌套关系,并生成相应的 TypeScript 代码。

使用自动化工具的优势显而易见:

  • 速度快: 瞬间处理大量 JSON 数据。
  • 减少错误: 避免手动转换时可能出现的拼写、类型或结构错误。
  • 处理复杂性: 轻松应对深层嵌套、复杂的联合类型或递归结构(某些高级工具支持)。

以下是一些常用的自动化工具:

1. 在线转换工具

这些工具通常提供一个网页界面,你只需粘贴 JSON 数据,它们就会在旁边生成 TypeScript 类型定义。

  • QuickType (quicktype.io): 功能强大,支持多种目标语言,包括 TypeScript。它能很好地处理复杂的联合类型、枚举、以及从多个 JSON 样本中推断共同结构。
  • transform.tools (JSON to TypeScript): 一个简洁易用的在线工具集合,其中包含 JSON 到 TypeScript 的转换器。
  • JSON to TS (json2ts.com): 另一个专门的 JSON 到 TypeScript 在线转换器。

如何使用(以 QuickType 为例):

  1. 打开 QuickType 网站 (quicktype.io)。
  2. 在左侧粘贴你的 JSON 数据。
  3. 在右侧选择目标语言为 TypeScript
  4. 工具会立即生成相应的 TypeScript 类型定义。
  5. 你可以调整一些选项,例如根类型的名称。
  6. 复制代码粘贴到你的项目中。

2. 编辑器扩展

许多流行的代码编辑器提供了插件或扩展,可以直接在编辑器中将粘贴板中的 JSON 转换为类型定义。

  • VS Code Extension: “Paste JSON as Code”: 这是 VS Code 中非常受欢迎的一个扩展。安装后,复制一段 JSON 数据,然后在 TypeScript 文件中打开命令面板 (Cmd+Shift+P 或 Ctrl+Shift+P),搜索并选择 “Paste JSON as Code”。扩展会询问你希望生成哪种语言的代码(选择 TypeScript),然后就会在当前光标位置粘贴生成的类型定义。这是日常开发中最便捷的方式之一。

如何使用 (VS Code “Paste JSON as Code”):

  1. 安装扩展。
  2. 复制你要转换的 JSON 数据。
  3. 在你的 .ts.tsx 文件中,将光标定位到你想插入类型定义的位置。
  4. 打开命令面板 (Ctrl+Shift+P)。
  5. 输入 “Paste JSON as Code” 并选择对应的命令。
  6. 选择 “TypeScript”。
  7. 生成的类型定义会直接插入到文件中。

3. 命令行工具/库

对于自动化工作流、构建脚本或需要处理大量 JSON 文件的情况,命令行工具或库更为合适。

  • quicktype CLI: QuickType 也提供命令行接口。你可以安装它 (npm install -g quicktype),然后通过命令转换文件或标准输入中的 JSON 数据。
    bash
    quicktype --lang ts < input.json > output.ts
  • json-to-ts: 一个专门的 Node.js 库和命令行工具。
    bash
    npm install -g json-to-ts
    json-to-ts input.json > output.ts

如何选择工具?

  • 快速一次性转换或探索 JSON 结构: 使用在线工具或 VS Code 扩展是最快的。
  • 集成到开发工作流,频繁转换: VS Code 扩展非常方便。
  • 自动化、构建脚本、CI/CD: 使用命令行工具。
  • 处理非常复杂的 JSON,或需要从多个样本中推断类型: QuickType (在线或 CLI) 通常表现出色。

自动化工具是提高效率的利器,但在使用时需要注意:

  • 依赖样本数据: 工具的生成结果质量高度依赖于你提供的 JSON 样本。如果样本不完整或不具代表性(例如,某个属性在样本中恰好缺失,但在实际数据中是必需的;或者某个属性在样本中是字符串,但在实际数据中偶尔是数字),生成的类型定义可能不准确。
  • 需要人工审查: 生成的类型定义应始终进行审查和调整,以确保它们与真实的 API 文档或数据行为一致,特别是处理可选属性、null 值、联合类型或枚举类型时。
  • 命名: 自动生成的类型名称可能不够语义化,通常需要手动修改。

第四部分:处理高级 JSON 结构与最佳实践

除了基本类型的转换,实际的 JSON 数据往往包含更复杂的结构。理解如何在 TypeScript 中准确表示这些结构是编写高质量类型定义的关键。

1. 数组 (Arrays)

正如前面提到的,数组的类型取决于其元素的类型。

  • 同类型元素的数组: string[], number[], boolean[], Address[], User[].
  • 不同类型元素的数组: 使用联合类型 (number | string)[], (Address | null)[].
  • 数组中的对象结构不确定(例如,异构数组): 如果数组包含结构不同的对象,这通常是一个设计不佳的 API。你可能需要定义一个包含所有可能属性的大型接口,并将不一定存在的属性标记为可选;或者使用联合类型 (TypeA | TypeB | TypeC)[] 并结合类型守卫在运行时判断实际类型。

2. 可选属性 (Optional Properties)

使用 ? 后缀标记属性名,表示该属性在 JSON 对象中可能不存在。

typescript
interface Product {
id: number;
name: string;
description?: string; // description 属性可能不存在
price: number;
}

当访问 product.description 时,TypeScript 知道它可能是 string | undefined

3. nullundefined

  • null: JSON 中的 null 值,在 TypeScript 中表示为 null 类型。如果一个属性可能接收 null 值,使用联合类型 Type | null
  • undefined: 在 TypeScript 中表示变量未赋值或对象属性不存在时的值。JSON 没有 undefined 值。但是,如果一个属性是可选的 (?),它的类型会隐含地包含 undefined

typescript
interface Settings {
theme: string | null; // theme 属性存在,值可能是 string 或 null
fontSize?: number; // fontSize 属性可能不存在 (undefined)
// 如果 fontSize 可能不存在,也可能存在但值为 null,则是 fontSize?: number | null;
}

开启 TypeScript 的 strictNullChecks 编译器选项可以强制你更严格地处理 nullundefined,提高代码安全性。

4. 联合类型 (Union Types)

当一个属性的值可以是多种类型中的任意一种时,使用 | 操作符创建联合类型。

typescript
interface Status {
value: "pending" | "processing" | "completed" | number; // 值可以是这几个字符串字面量之一,也可以是数字
}

这在处理状态字段或可能返回不同类型数据的 API 终点时非常有用。

5. 字面量类型 (Literal Types) 和 枚举 (Enums)

如果一个属性的值限定在几个特定的字符串或数字中,可以使用字面量类型或 TypeScript 的 enum

  • 字面量类型:
    typescript
    interface Event {
    type: "click" | "hover" | "scroll"; // 值必须是这三个字符串之一
    timestamp: number;
    }
  • 枚举 (Enum): 如果字面量有很多或者需要在代码中作为可枚举的值使用,enum 更合适。但请注意,字符串枚举在编译后会保留字符串,而数字枚举默认会生成双向映射的代码,可能会增加一些体积。
    “`typescript
    enum UserRole {
    Admin = “admin”,
    Editor = “editor”,
    Viewer = “viewer”
    }

    interface User {
    // … 其他属性
    role: UserRole; // 值必须是 UserRole 枚举中的一个
    }
    “`

6. 交叉类型 (Intersection Types)

使用 & 操作符将多个类型合并为一个新类型,新类型拥有所有被合并类型的成员。这在组合来自不同源的数据结构时很有用。

“`typescript
interface Timestamp {
createdAt: string;
updatedAt: string;
}

interface UserData {
id: number;
name: string;
}

// 一个用户对象,同时包含 UserData 和 Timestamp 的属性
type UserWithTimestamps = UserData & Timestamp;
“`

7. 索引签名 (Index Signatures)

当你处理一个对象,其属性名是动态的,但你知道属性值的类型时,可以使用索引签名。例如,一个表示语言映射的对象:

json
{
"en": "Hello",
"es": "Hola",
"fr": "Bonjour"
}

对应的 TypeScript 类型:

typescript
interface Translations {
[key: string]: string; // 任何字符串作为属性名,其值必须是 string
}

或者如果属性名限定在某些已知值中:

“`typescript
interface SpecificTranslations {

}
“`

8. 递归类型 (Recursive Types)

处理树形结构或嵌套层级不定的 JSON 时,类型定义可能需要引用自身。

json
{
"name": "Root",
"children": [
{
"name": "Child 1",
"children": []
},
{
"name": "Child 2",
"children": [
{
"name": "Grandchild 1",
"children": []
}
]
}
]
}

对应的 TypeScript 类型:

typescript
interface TreeNode {
name: string;
children: TreeNode[]; // 这里的 children 属性引用了 TreeNode 自身
}

最佳实践:

  • 从实际数据出发,结合文档: 始终根据真实的 JSON 响应样本和 API 文档来定义类型。单一样本可能不包含所有可能的属性或值类型。
  • 优先使用 interface 表示对象形状: interface 在描述对象的结构时通常更直观,且支持声明合并(虽然在处理 JSON 时不常用)。
  • 使用 type 进行类型别名、联合、交叉和字面量类型: type 的灵活性使其成为定义非对象结构或组合现有类型的首选。
  • 保持类型定义文件组织有序: 将相关的类型定义放在一起,可以按模块或 API 终点组织文件。
  • 给类型和属性起有意义的名称: 清晰的命名能显著提高代码的可读性。
  • 不要过度追求精确(初期): 对于非常复杂的 JSON,可以先从核心字段开始定义,逐步完善。有时,完全精确的类型定义会异常复杂,适度的妥协(例如,对某些复杂或不常用的字段使用 anyunknown,并辅以运行时检查)可能是必要的。但要尽量避免 any
  • 定期审查和更新类型: 随着后端 API 的变化,及时更新前端的类型定义,确保类型与实际数据同步。

第五部分:在项目中使用生成的类型

生成了 TypeScript 类型定义后,下一步就是如何在你的 TypeScript 代码中使用它们。

  1. 导入类型: 如果你的类型定义在单独的文件中(例如 types.ts),你需要在需要使用它们的文件中导入。
    “`typescript
    // types.ts
    export interface User {
    id: number;
    name: string;
    // …
    }

    // some-module.ts
    import { User } from ‘./types’;

    // …
    “`

  2. 标注变量和函数参数/返回值: 将 JSON 数据赋值给变量时,为变量添加类型标注;处理 JSON 数据的函数,标注其参数和返回值类型。
    “`typescript
    import { User } from ‘./types’;

    async function fetchUser(userId: number): Promise {
    const response = await fetch(/api/users/${userId});
    if (!response.ok) {
    throw new Error(‘Failed to fetch user’);
    }
    const userData: User = await response.json(); // 标注接收到的数据类型
    return userData;
    }

    // 使用
    fetchUser(123).then(user => {
    console.log(user.name); // IDE 提供了 auto-completion
    console.log(user.address.city); // 类型安全访问嵌套属性
    // console.log(user.nonExistentProperty); // 编译器会报错
    });
    “`

  3. 处理可选属性和 null 当访问可能为 nullundefined 的属性时,TypeScript 会强制你进行检查。
    “`typescript
    import { User, ProfileData } from ‘./types’;

    function displayUserProfile(user: User) {
    // 假设 profile?: ProfileData | null;

    if (user.profile) { // 检查 profile 是否存在且非 null
    console.log(user.profile.bio); // 在这个块内,profile 被窄化为 ProfileData
    } else {
    console.log(“No profile data available.”);
    }

    // 或者使用可选链 (Optional Chaining) 如果只需要访问嵌套属性
    console.log(user.profile?.website); // 如果 user.profile 是 null 或 undefined,结果是 undefined
    }
    “`

  4. 类型断言 (Type Assertion)(谨慎使用): 如果你确定某个变量的类型,但 TypeScript 无法自动推断,可以使用 as Type 进行类型断言。但请注意,类型断言会绕过 TypeScript 的类型检查,如果断言错误,可能会导致运行时错误。
    typescript
    const unknownData: any = JSON.parse(jsonString);
    const userData = unknownData as User; // 告诉 TypeScript,unknownData 就是一个 User 类型
    // 风险:如果 unknownData 实际不是 User 类型,这里不会报错,但后续使用 userData 时可能出错

    通常更推荐的方法是先定义类型,然后确保数据的来源(如 fetch 请求的结果)在赋值给该类型变量时是兼容的,或者在接收到数据后进行运行时验证。

第六部分:总结

将 JSON 数据转换为 TypeScript 类型定义是现代 TypeScript 前端开发中不可或缺的一环。它将原本动态、无模式的数据赋予了静态结构,从而带来了显著的代码质量、开发效率和应用健壮性提升。

本文详细介绍了:

  • 必要性: 弥合 JSON 的动态性与 TypeScript 静态性之间的差距。
  • 手动转换: 理解基本对应关系和步骤,适用于简单场景或理解工具原理。
  • 自动化工具: 利用在线工具、编辑器扩展或命令行工具高效准确地生成类型定义,适用于复杂和大量的 JSON 数据。
  • 高级结构: 如何处理数组、可选属性、null、联合类型、字面量类型、枚举、交叉类型、索引签名和递归类型等。
  • 使用方法: 如何在代码中导入、标注和安全地使用这些类型。

无论是手动还是自动化,核心目标都是为 JSON 数据建立清晰、准确的类型契约。在实际开发中,通常会结合使用手动调整和自动化工具。从可靠的 JSON 样本出发,利用工具快速生成基础类型,然后根据 API 文档和对数据结构的深入理解,手动审查和优化生成的类型定义,特别是处理可选性、null 值和各种联合类型。

掌握 JSON 到 TypeScript 类型定义的过程,是编写可维护、类型安全的前端应用的关键一步。花时间为你的数据定义好类型,将在项目的整个生命周期中获得巨大的回报。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部