在TypeScript中推断对象和函数类型(附实例)

252 阅读3分钟

TypeScript强大的推理功能可以帮助我们避免用大量的类型注解来膨胀我们的代码。typeof 关键字可以帮助我们从另一个变量的类型中强加一个变量的类型。

让我们看一个例子,这在React中很有用。

获取一个对象的类型

下面是React中强类型表单的一个片段:

const defaultState = { name: "", email: "" };
const App = () => {
  const [values, setValues] = React.useState(defaultState);  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    save(values);  };
  ...
};

values 状态从defaultState 变量中推断为{ name: string, email: string } 的类型。

handleSubmit ,我们在组件外调用一个save 函数,它将是这样的:

const save = (data) => {
  // post the data to the server ...
};

我们是在TypeScript的土地上,所以我们需要给data 参数一个类型注释。这是否意味着我们现在必须为其创建一个类型?幸运的是,我们可以使用typeof 关键字来推断出defaultState 的类型:

const save = (data: typeof defaultState) => {
  // post the data to the server ...
};

你可以在CodeSandbox中看到这个例子:codesandbox.io/s/typeof-uy…

获取一个函数的返回类型

有时,获取一个函数的返回类型是很有用的。

一个例子是在Redux代码中,我们想为所有的动作创建一个联合类型,以便在action reducer函数参数上使用。因此,如果我们有以下的动作创建者:

function addPerson(personName: string) {
  return {
    type: "AddPerson",
    payload: personName,
  } as const;
}

function removePerson(id: number) {
  return {
    type: "RemovePerson",
    payload: id,
  } as const;
}

......我们想创建一个联合类型,如下所示:

type Actions =
  | return type of addPerson
  | return type of removePerson

这不是有效的语法,但希望你能明白我们要做什么。

幸运的是,有一个很方便的实用类型叫做 ReturnType的实用类型,我们可以使用它:

type Actions =
  | ReturnType<typeof addPerson>
  | ReturnType<typeof removePerson>;

这正好给了我们所需要的类型:

{
    readonly type: "AddPerson";
    readonly payload: string;
} | {
    readonly type: "RemovePerson";
    readonly payload: number;
}

很好!

获取异步函数的返回类型

如果函数是异步的呢?

async function addPersonAsync(
  personName: string
) {
  await wait(200);
  return {
    type: "AddPerson",
    payload: personName,
  } as const;
}

async function removePersonAsync(id: number) {
  await wait(200);
  return {
    type: "RemovePerson",
    payload: id,
  } as const;
}

type ActionsAsync =
  | ReturnType<typeof addPersonAsync>
  | ReturnType<typeof removePersonAsync>;

ActionsAsync 类型并不完全是我们所需要的:

Promise<{
    readonly type: "AddPerson";
    readonly payload: string;
}> | Promise<{
    readonly type: "RemovePerson";
    readonly payload: number;
}>

我们想要的是承诺被解析后的类型。

让我们看一下ReturnType 的定义:

type ReturnType<
  T extends (...args: any) => any
> = T extends (...args: any) => infer R
  ? R
  : any;

这看起来有点棘手,所以,让我们把它分解一下:

  • 该类型是一个通用类型,其中的参数具有签名T extends (...args: any) => any 。即我们需要传递一个函数类型,否则会发生类型错误。
  • 该类型是一个条件类型,条件是参数是否有一个函数签名。
  • infer R 是条件中很有价值的一点,因为这把通用参数的返回类型放到了 参数中。R
  • 如果条件是true (即通用参数是一个函数),则返回R (即该函数的返回类型)。
  • any 如果条件是 ,则返回 。false

这就解释了为什么我们的异步函数的返回类型中包含了Promise

让我们来试试为异步函数的返回类型创建我们自己的实用类型:

type ReturnTypeAsync<
  T extends (...args: any) => any
> = T extends (...args: any) => Promise<infer R>
  ? R
  : any;

所以我们的Actions 联盟类型变成了:

type ActionsAsync =
  | ReturnTypeAsync<typeof addPersonAsync>
  | ReturnTypeAsync<typeof removePersonAsync>;

这就给出了我们期望的类型:

{
    readonly type: "AddPerson";
    readonly payload: string;
} | {
    readonly type: "RemovePerson";
    readonly payload: number;
}

如果我们想聪明一点,改进ReturnTypeAsync ,使其既适用于异步函数,也适用于同步函数,我们可以这样做:

type ReturnTypeAsync<
  T extends (...args: any) => any
> = T extends (...args: any) => Promise<infer R>
  ? R
  : T extends (...args: any) => infer R
  ? R
  : any;

Neat!

你可以在CodeSandbox中看到这个例子:codesandbox.io/s/function-…