适用于 React 的极简 TypeScript 速成课程 - 包含交互式代码练习

2025-06-07

适用于 React 的极简 TypeScript 速成课程 - 包含交互式代码练习

即使你还没有使用过 TypeScript,你可能也听说过它。过去几年,它在 React 领域得到了广泛的应用。目前,几乎所有 React 工作似乎都需要 TypeScript 知识。

因此,许多 React 开发人员问自己:我真的需要学习 TypeScript 吗?

我明白,你已经有很多事情要做了。尤其是如果你还在努力进入这个行业,你可能会被各种需要学习的东西压得喘不过气来。但请听听这位开发者的分享:

开发人员对 TypeScript 与 React 的看法

学习 TypeScript 无疑是最好的投资:

  • 您获得工作机会会增加。
  • 您的代码将会更少错误并且更易于阅读/维护。
  • 重构代码和更新依赖项将变得更加容易。

简而言之,你的开发经验将会飞速提升。不过,一开始你可能会遇到一些困难。各种奇怪的错误,以及像被绑在背后(还有剩下的四根手指被粘在一起)一样的编码。

本页面旨在帮助您快速上手,避免繁琐的细节。您将获得关于如何在 React 中使用 TypeScript 的简要介绍。为了巩固所学知识,几乎每个章节后都提供了交互式代码练习

由于这个话题很容易变得枯燥,所以我们大致跟随这个故事的反英雄:帕特,一个讨厌的首席技术官。

请注意,本页练习使用的代码编辑器比较新。如果您遇到问题,请发送错误报告至bugs@profy.dev

React 所需的 TypeScript 基础知识

原语

它们的名字正在逐渐消失,但这三个原始类型却是所有类型的核心。

string // e.g. "Pat"
boolean // e.g. true
number // e.g. 23 or 1.99
Enter fullscreen mode Exit fullscreen mode

数组

数组可以由原语或任何其他类型构建。

number[] // e.g. [1, 2, 3]
string[] // e.g. ["Lisa", "Pat"]
User[] // custom type e.g. [{ name: "Pat" }, { name: "Lisa" }]
Enter fullscreen mode Exit fullscreen mode

对象

对象无处不在,它们可以发挥巨大的作用。就像这个例子一样:

const user = {
  firstName: "Pat",
  age: 23,
  isNice: false,
  role: "CTO",
    skills: ["CSS", "HTML", "jQuery"]
}
Enter fullscreen mode Exit fullscreen mode

真是个“CTO”!描述此对象的 TypeScript 类型如下所示:

type User = {
  firstName: string;
  age: number;
  isNice: boolean;
  role: string,
  skills: string[];
}
Enter fullscreen mode Exit fullscreen mode

显然,人类并不只由原始人组成。

type User = {
  firstName: string;
  ...
    friends: User[];
}
Enter fullscreen mode Exit fullscreen mode

但看看我们的“CTO”,我们可以把字段设为可选,这很好。Pat 显然选择了事业而不是朋友:

const user = {
  firstName: "Pat",
  age: 23,
  isNice: false,
  role: "CTO",
    skills: ["CSS", "HTML", "jQuery"],
    friends: undefined
}
Enter fullscreen mode Exit fullscreen mode

尽管这对 Pat 来说可能很困难,但在 TypeScript 中,这就像添加以下内容一样简单?

type User = {
  firstName: string;
  ...
    friends?: User[];
}
Enter fullscreen mode Exit fullscreen mode

实时编码练习

枚举

请记住,我们将User.role字段定义为string

type User = {
  ...
  role: string,
}
Enter fullscreen mode Exit fullscreen mode

作为“首席技术官”的帕特对此很不高兴。他知道这种string类型的限制性不够。他的员工不应该能够随意选择他们想要的角色。

枚举来拯救他!

enum UserRole {
  CEO,
  CTO,
  SUBORDINATE,
}
Enter fullscreen mode Exit fullscreen mode

这下好多了!不过 Pat 也不傻:他知道这个枚举值在内部只是数字。虽然 CEO 位居最高(拍马屁的 Pat),但它的数值是0。零!CTO 是 1。下属是 2?

好像不太合适。怎么每个人都比领导更有价值呢?

幸运的是,我们可以使用字符串值作为枚举。

enum UserRole {
  CEO = "ceo",
  CTO = "cto",
  SUBORDINATE = "inferior-person",
}
Enter fullscreen mode Exit fullscreen mode

当你想传达信息的时候,这非常有用(Pat,我指的是你)。但处理来自 API 的字符串时,它也很有用。

不管怎样,帕特现在很高兴了。他可以放心地给每个人分配合适的角色了。

enum UserRole {
  CEO = "ceo",
  CTO = "cto",
  SUBORDINATE = "inferior-person",
}

type User = {
  firstName: string;
  age: number;
  isNice: boolean;
  role: UserRole,
  skills: string[];
  friends?: User[];
}

const user = {
  firstName: "Pat",
  age: 23,
  isNice: false,
  role: UserRole.CTO, // equals "cto"
    skills: ["CSS", "HTML", "jQuery"]
}
Enter fullscreen mode Exit fullscreen mode

实时编码练习

功能

掌权者最喜欢做什么?我不确定,但帕特肯定喜欢通过解雇一群失败者来炫耀自己的权力。

因此,让我们让 Pat 高兴并编写一个可以提高他的射击性能的函数。

函数参数的类型

我们有三种方法来识别被解雇的人。首先,我们可以使用多个参数。

function fireUser(firstName: string, age: number, isNice: boolean) {
  ...
}

// alternatively as an arrow function
const fireUser = (firstName: string, age: number, isNice: boolean) => {
  ...
}
Enter fullscreen mode Exit fullscreen mode

其次,我们可以将所有上述参数包装在一个对象中并以内联方式定义类型。

function fireUser({ firstName, age, isNice }: {
  firstName: string;
  age: number;
  isNice: boolean;
}) {
  ...
}
Enter fullscreen mode Exit fullscreen mode

最后(由于上面的代码可读性不高),我们还可以提取类型。剧透警告:这是我们在 React 组件 props 中经常看到的做法。

type User = {
  firstName: string;
  age: number;
  role: UserRole;
}

function fireUser({ firstName, age, role }: User) {
  ...
}

// alternatively as arrow function
const fireUser = ({ firstName, age, role }: User) => {
  ...
}
Enter fullscreen mode Exit fullscreen mode

函数返回值的类型

简单地解雇一个用户可能对 Pat 来说还不够。也许他想进一步侮辱他们?所以,从函数中返回用户可能是个好主意。

同样,定义返回类型也有多种方法。首先,我们可以: MyType在参数列表的右括号后面添加。

function fireUser(firstName: string, age: number, role: UserRole): User {
  // some logic to fire that loser ...
  return { firstName, age, role };
}

// alternatively as an arrow function
const fireUser = (firstName: string, age: number, role: UserRole): User => {
  ...
}
Enter fullscreen mode Exit fullscreen mode

这强制返回正确的类型。如果我们尝试返回其他类型(例如null),TypeScript 不会允许我们这么做。

TypeScript 强制执行正确的返回类型

如果我们不想那么严格,我们也可以让 TypeScript 推断返回类型。

function fireUser(user: User) {
  // some logic to fire that loser ...
  return user;
}
Enter fullscreen mode Exit fullscreen mode

这里我们只是返回输入参数。这也可以作为“触发”逻辑的返回值。但由于返回值的类型user很明确,TypeScript 会自动识别函数的返回类型。

TypeScript 推断返回值

所以,使用 TypeScript 时,越少越好。你不需要到处定义类型,通常可以依赖类型推断。如有疑问,只需将鼠标光标悬停在相关变量或函数上即可,如上图所示。

实时编码练习

你可能会遇到的事情

这里还有一些我们未曾提及但您很快就会遇到的事情。

在职 React - 电子邮件课程

使用 TypeScript 进行 React

对于 React 和 TypeScript 来说,以上章节的基础知识通常就足够了。毕竟,React 函数组件和 hooks 都是简单的函数。而 props 只是对象。在大多数情况下,你甚至不需要定义任何类型,因为 TypeScript 知道如何推断它们。

使用 TypeScript 的函数组件

大多数情况下,你不需要定义组件的返回类型。你肯定会指定 props 的类型(我们稍后会看到)。但是,一个简单的无 props 组件不需要任何类型。

function UserProfile() {
  return <div>If you're Pat: YOU'RE AWESOME!!</div>
}
Enter fullscreen mode Exit fullscreen mode

返回类型JSX.Element正如您在该屏幕截图中所看到的。

React 组件返回 JSX.Element 类型

很棒的事情是:如果我们搞砸了并从组件返回任何无效的 JSX 内容,TypeScript 会警告我们。

当组件返回无效的 JSX 时,TypeScript 会发出警告

在这种情况下,user对象不是有效的 JSX,因此我们会收到错误:

'UserProfile' cannot be used as a JSX component.
Its return type 'User' is not a valid JSX element.
Enter fullscreen mode Exit fullscreen mode

实时编码练习

使用 TypeScript 的 Props

这番话UserProfile确实夸奖了我们的“CTO”Pat。但它却向所有用户传达了同样的信息,感觉像是在侮辱他。显然,我们需要一些鼓励。

enum UserRole {
  CEO = "ceo",
  CTO = "cto",
  SUBORDINATE = "inferior-person",
}

type UserProfileProps = {
  firstName: string;
  role: UserRole;
}

function UserProfile({ firstName, role }: UserProfileProps) {
    if (role === UserRole.CTO) {
    return <div>Hey Pat, you're AWESOME!!</div>
  }
  return <div>Hi {firstName}, you suck!</div>
}
Enter fullscreen mode Exit fullscreen mode

如果您更喜欢箭头函数,相同的组件看起来像这样。

const UserProfile = ({ firstName, role }: UserProfileProps) => {
    if (role === UserRole.CTO) {
    return <div>Hey Pat, you're AWESOME!!</div>
  }
  return <div>Hi {firstName}, you suck!</div>
}
Enter fullscreen mode Exit fullscreen mode

最后说明一下:在实际中,你会发现很多代码使用React.FCReact.FunctionComponent来输入组件。不过现在不再推荐这样做了。

// using React.FC is not recommended
const UserProfile: React.FC<UserProfileProps>({ firstName, role }) {
  ...
}
Enter fullscreen mode Exit fullscreen mode

实时编码练习

回调属性

众所周知,在 React 中我们经常将回调函数作为 props 传递。目前为止我们还没有见过 function 类型的字段。所以让我快速展示一下:

type UserProfileProps = {
  id: string;
  firstName: string;
  role: UserRole;
  fireUser: (id: string) => void;
};

function UserProfile({ id, firstName, role, fireUser }: UserProfileProps) {
  if (role === UserRole.CTO) {
    return <div>Hey Pat, you're AWESOME!!</div>;
  }
  return (
    <>
      <div>Hi {firstName}, you suck!</div>
      <button onClick={() => fireUser(id)}>Fire this loser!</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

注意:void是函数的返回类型,代表“无”。

实时编码练习

默认道具

记住:我们可以用 标记一个字段,使其成为可选字段?。我们也可以对可选的 props 执行相同的操作。这里的role不是必需的。

type UserProfileProps = {
  age: number;
  role?: UserRole;
}
Enter fullscreen mode Exit fullscreen mode

如果我们想要为可选道具设置默认值,我们可以在重组道具时分配它。

function UserProfile({ firstName, role = UserRole.SUBORDINATE }: UserProfileProps) {
  if (role === UserRole.CTO) {
    return <div>Hey Pat, you're AWESOME!!</div>
  }
  return <div>Hi {firstName}, you suck!</div>
}
Enter fullscreen mode Exit fullscreen mode

实时编码练习

使用 TypeScript 的 useState Hook(推断类型)

React 中最常用的 hook 是useState()。很多情况下,你不需要指定它的类型。如果你使用初始值,TypeScript 可以推断出它的类型。

function UserProfile({ firstName, role }: UserProfileProps) {
  const [isFired, setIsFired] = useState(false);
  return (
    <>
      <div>Hi {firstName}, you suck!</div>
      <button onClick={() => setIsFired(!isFired)}>
        {isFired ? "Oops, hire them back!" : "Fire this loser!"}
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

TypeScript 推断 React 状态的类型

现在我们安全了。如果我们尝试使用布尔值以外的任何其他值来更新状态,就会出现错误。

将错误类型设置为状态时,TypeScript 会发出警告

在职 React - 电子邮件课程

使用 TypeScript 的 useState Hook(手动输入)

不过,在其他情况下,TypeScript 无法根据初始值正确推断类型。例如:

// TypeScript doesn't know what type the array elements should have
const [names, setNames] = useState([]);

// The initial value is undefined so TS doesn't know its actual type
const [user, setUser] = useState();

// Same story when we use null as initial value
const user = useState(null);
Enter fullscreen mode Exit fullscreen mode

在某些情况下,TypeScript 无法推断状态的类型

useState()是用所谓的泛型类型实现的。我们可以用它来正确地定义状态的类型:

// the type of names is string[]
const [names, setNames] = useState<string[]>([]);
setNames(["Pat", "Lisa"]);

// the type of user is User | undefined (we can either set a user or undefined)
const [user, setUser] = useState<User>();
setUser({ firstName: "Pat", age: 23, role: UserRole.CTO });
setUser(undefined);

// the type of user is User | null (we can either set a user or null)
const [user, setUser] = useState<User | null>(null);
setUser({ firstName: "Pat", age: 23, role: UserRole.CTO });
setUser(null);
Enter fullscreen mode Exit fullscreen mode

这应该足够你使用 了useState()。由于我们专注于 React 的最低 TypeScript 技能,所以这里就不讨论其他钩子了。只需注意useEffect()不需要输入。

实时编码练习

使用 TypeScript 自定义 Hooks

自定义钩子同样只是一个函数。所以你已经知道如何定义它了。

function useFireUser(firstName: string) {
    const [isFired, setIsFired] = useState(false);
  const hireAndFire = () => setIsFired(!isFired);

    return {
    text: isFired ? `Oops, hire ${firstName} back!` : "Fire this loser!",
    hireAndFire,
  };
}

function UserProfile({ firstName, role }: UserProfileProps) {
  const { text, hireAndFire } = useFireUser(firstName);
  return (
    <>
      <div>Hi {firstName}, you suck!</div>
      <button onClick={hireAndFire}>
        {text}
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

实时编码练习

使用 TypeScript 来响应事件

使用内联点击处理程序非常简单,因为 TypeScript 已经知道事件参数的类型。您无需手动输入任何内容。

function FireButton() {
  return (
    <button onClick={(event) => event.preventDefault()}>
      Fire this loser!
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

但是,当您创建单独的点击处理程序时,它会变得更加复杂。

function FireButton() {
  const onClick = (event: ???) => {
    event.preventDefault();
  };

  return (
    <button onClick={onClick}>
      Fire this loser!
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

这里的类型是什么event?这里有两种方法:

  1. 在 Google 上搜索(不推荐,会让你头晕)。
  2. 假设您有一个内联函数,并让 TypeScript 为您提供该类型。

将鼠标悬停在内联函数参数上以获取正确的事件类型

复制粘贴愉快!你甚至不需要理解这里发生了什么(提示:这些都是泛型,就像我们在 中看到的那样useState)。

function FireButton() {
  const onClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    event.preventDefault();
  };

  return (
    <button onClick={onClick}>
      Fire this loser!
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

那么输入的变更处理程序呢?同样的策略可以揭示:

function Input() {
  const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    console.log(event.target.value);
  };

  return <input onChange={onChange} />;
}
Enter fullscreen mode Exit fullscreen mode

还有选择吗?

function Select() {
  const onChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    console.log(event.target.value);
  };

  return <select onChange={onChange}>...</select>;
}
Enter fullscreen mode Exit fullscreen mode

实时编码练习

子类型或组件类型

由于我们都是组件组合的粉丝,所以我们需要知道如何输入常见的childrenprop。

type LayoutProps = {
  children: React.ReactNode;
};

function Layout({ children }: LayoutProps) {
  return <div>{children}</div>;
}
Enter fullscreen mode Exit fullscreen mode

这种类型React.ReactNode赋予了我们很大的自由度。它基本上允许我们将任何东西作为子对象传递(对象除外)。

React.ReactNode 类型允许除对象之外的所有内容

如果我们想要更严格一些并且只允许标记,我们可以使用React.ReactElementJSX.Element(基本上相同)。

type LayoutProps = {
  children: React.ReactElement; // same as JSX.Element
};
Enter fullscreen mode Exit fullscreen mode

正如您所见,这更加严格:

React.ReactElement 类型仅允许 JSX

在职 React - 电子邮件课程

使用第三方库

添加类型

如今,许多第三方库已经自带了相应的类型。在这种情况下,您无需再安装单独的软件包。

但是许多类型也由 GitHub 上的DefinitelyTyped 仓库维护,并由该@types组织发布(甚至包括 React 类型)。如果你安装一个没有类型的库,导入语句中会报错。

许多第三方库需要@types注册表中的附加类型

您可以简单地复制并粘贴突出显示的命令并在终端中执行它。

npm i --save-dev @types/styled-components
Enter fullscreen mode Exit fullscreen mode

大多数情况下,你会很幸运,所需的类型可以通过某种方式获得。尤其是对于一些比较流行的库来说。但即使如此,你仍然可以在.d.ts文件中定义自己的全局类型(不过我们这里就不讲了)。

使用泛型

库通常需要处理大量用例。因此,它们需要灵活。为了定义灵活的类型,需要使用泛型。我们useState已经见过它们了。这里提醒一下:

const [names, setNames] = useState<string[]>([]);
Enter fullscreen mode Exit fullscreen mode

这在很多第三方库中很常见,这里以 Axios 为例:

import axios from "axios"

async function fetchUser() {
  const response = await axios.get<User>("https://example.com/api/user");
  return response.data;
}
Enter fullscreen mode Exit fullscreen mode

第三方库经常使用泛型

或者 react-query:

import { useQuery } from "@tanstack/react-query";

function UserProfile() {
  // generic types for data and error
  const { data, error } = useQuery<User, Error>(["user"], () => fetchUser());

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  ...
}
Enter fullscreen mode Exit fullscreen mode

或者样式组件:

import styled from "styled-components";

// generic type for props
const MenuItem = styled.li<{ isActive: boolean }>`
  background: ${(props) => (props.isActive ? "red" : "gray")};
`;

function Menu() {
  return (
    <ul>
      <MenuItem isActive>Menu Item 1</MenuItem>
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

策略与故障排除

React 和 TypeScript 入门

使用 TypeScript 创建新项目是最简单的选择。我建议创建一个 Vite + React + TypeScript 项目,或者创建一个 Next.js + TypeScript 项目。

// for Vite run this command and select "react-ts"
npm create vite@latest

// for Next.js run
npx create-next-app@latest --ts
Enter fullscreen mode Exit fullscreen mode

这将完全自动地为您设置。

找到合适的类型

我们已经讨论过这一点,但让我在这里重复一遍:当有疑问时(尤其对事件有用)只需开始输入内联函数,然后让 TypeScript 向您显示正确的类型。

将鼠标悬停在内联函数参数上以获取正确的事件类型

如果你不确定有多少个参数可用,也可以这样做。只需写入(...args) =>,即可将所有参数放入数组中。

使用扩展运算符获取所有函数参数

检查类型

查看类型上所有可用字段的最简单方法是使用 IDE 的自动完成功能。此处按 CTRL + 空格键(Windows)或 Option + 空格键(Mac)。

使用自动完成建议检查 TypeScript 类型

但有时你需要更深入地了解。这很简单(但常常令人困惑),只需按 CTRL + 单击(Windows)或 CMD + 单击(Mac)即可转到类型定义。

通过遵循定义检查 TypeScript 类型

在职 React - 电子邮件课程

读取错误消息

刚开始使用 TypeScript 时,一个主要问题是遇到的各种错误。所有可能出错的地方都可能让你抓狂。所以,养成阅读错误信息的习惯是个好主意。我们来看一个例子:

function Input() {
  return <input />;
}

function Form() {
  return (
    <form>
      <Input onChange={() => console.log("change")} />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

你可能已经看到了问题所在。不过,下面是 TypeScript 显示的错误。

TypeScript 错误消息:prop 未定义

有点困惑!这到底是什么意思? type 又是什么IntrinsicAttributes?在使用库(例如 React 本身)时,你会遇到很多像这样的奇怪类型名称。

我的建议是:暂时忽略它们。

最重要的信息在最后一行:

Property 'onChange' does not exist on type ...
Enter fullscreen mode Exit fullscreen mode

是不是有点熟悉?看一下<Input>组件的定义:

function Input() {
  return <input />;
}
Enter fullscreen mode Exit fullscreen mode

它没有一个名为 的 prop onChange。这就是 TypeScript 所抱怨的。

这是一个相当简单的例子。但是接下来呢?

const MenuItem = styled.li`
  background: "red";
`;

function Menu() {
  return <MenuItem isActive>Menu Item</MenuItem>;
}
Enter fullscreen mode Exit fullscreen mode

TypeScript 错误消息:没有重载与此调用匹配

天哪!这里输出的错误信息量太大了,很容易让人看晕。我最常用的技巧是滚动到消息底部。很多时候,金块就埋在那里。

TypeScript 错误信息:没有重载匹配此调用解决方案在底部

function UserProfile() {
  const { data, error } = useQuery(
    ["user"],
    {
      cacheTime: 100000,
    },
    () => fetchUser()
  );
}
Enter fullscreen mode Exit fullscreen mode

对象作为更清洁类型的道具

在上面的例子中,用户数据通常来自 API。假设我们在User组件外部定义了类型

export type User = {
  firstName: string;
  role: UserRole;
}
Enter fullscreen mode Exit fullscreen mode

UserProfile此刻的组件正是以这个对象作为道具

function UserProfile({ firstName, role }: User) { 
  ...
}
Enter fullscreen mode Exit fullscreen mode

目前看来这似乎合理。事实上,当我们有现成的用户对象时,渲染这个组件相当容易。

function UserPage() {
  const user = useFetchUser();
  return <UserProfile {...user} />;
}
Enter fullscreen mode Exit fullscreen mode

但是,一旦我们想要添加User类型中没有的额外 props,事情就变得困难了。还记得fireUserLOL上面的函数吗?让我们来使用它。

function UserProfile({ firstName, role, fireUser }: User) {
  return (
    <>
      <div>Hi {firstName}, you suck!</div>
      <button onClick={() => fireUser({ firstName, role })}>
        Fire this loser!
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

但由于该fireUser函数未在User类型上定义,因此我们会收到错误。

混合不同类型的道具

为了创建正确的类型,我们可以使用所谓的交叉类型,即将两种类型与 组合起来&。这基本上是将两种不同类型的所有字段合并为一种类型。

type User = {
  firstName: string;
  role: UserRole;
}

// this is called an intersection
// UserProfileProps has all fields of both types
type UserProfileProps = User & {
    fireUser: (user: User) => void;
}

function UserProfile({ firstName, role, fireUser }: UserProfileProps) {
  return (
    <>
      <div>Hi {firstName}, you suck!</div>
      <button onClick={() => fireUser({ firstName, role })}>
        Fire this loser!
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

相比于交叉类型,分离类型通常更简洁。在我们的例子中,我们可以使用用户 prop,而不是直接接受所有用户字段。

type User = {
  firstName: string;
  role: UserRole;
}

type UserProfileProps = {
  user: User;
    fireUser: (user: User) => void;
}

function UserProfile({ user, onClick }: UserProfileProps) {
  return (
    <>
      <div>Hi {user.firstName}, you suck!</div>
      <button onClick={() => fireUser(user)}>
        Fire this loser!
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

实时编码练习

在职 React - 电子邮件课程

文章来源:https://dev.to/jkettmann/minimal-typescript-crash-course-for-react-with-interactive-code-exercises-43cl
PREV
React 和 REST API:基于 OpenAPI 文档的端到端 TypeScript
NEXT
像自由职业者一样自由,还是像正式员工一样有保障?还有第三种选择:承包商 获取免费的 Dev Contractor 路线图