浅谈 TypeScript 中的 Utility Types

303 阅读2分钟

最近在看 XFlow 的源码,发现里面出现了 Record、Omit、ReturnType 这些奇怪的东西,查了官方文档后才知道它们被称为 Utility Types,可以理解为内置的一些工具函数,会基于传入的泛型参数返回新的 ts 类型。这里做了一些简单的整理和说明,更具体的可以参考官方文档

定义

TypeScript 提供了几种 Utility Types 来促进常见的类型转换。这些 Utility Types 可以在全局作用域使用

Partial

构造一个类型,其中Type 的所有属性都设置为可选。可以理解为 Partial<Type> 返回 Type 的所有子类型

interface Todo {
  title: string;
  description: string;
}
 
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}
 
const todo1 = {
  title: "organize desk",
  description: "clear clutter",
};
 
const todo2 = updateTodo(todo1, {
  description: "throw out trash",
});

Record<Keys, Type>

构造一个对象类型,其属性键为 Keys,其属性值为 TypeRecord<Keys, Type> 可用于将一种类型的属性映射到另一种类型。

interface CatInfo {
  age: number;
  breed: string;
}
 
type CatName = "miffy" | "boris" | "mordred";
 
const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: "Persian" },
  boris: { age: 5, breed: "Maine Coon" },
  mordred: { age: 16, breed: "British Shorthair" },
};
 
cats.boris;

在 XFlow 中如果通过 object 的方式定义映射类型会给出警告。

image.png

image.png

正确的方式应该是写成这样,是不是感觉简洁了很多,而且表意性更强

image.png

Pick<Type, Keys>

这个顾名思义,就是挑选出一些属性,看了例子就很好理解了。

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
 
type TodoPreview = Pick<Todo, "title" | "completed">;
 
const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};

这里的type TodoPreview = Pick<Todo, "title" | "completed">其实就等价于

type TodoPreview = {
  title: string;
  completed: boolean;
}

Omit<Type, Keys>

omit 在英语中是弹出的意思,那这里也就很好理解了,就是移除掉一些属性,同样看例子

interface Todo {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}
 
type TodoPreview = Omit<Todo, "description">;
 
const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
  createdAt: 1615544252770,
};
 
type TodoInfo = Omit<Todo, "completed" | "createdAt">;
 
const todoInfo: TodoInfo = {
  title: "Pick up kids",
  description: "Kindergarten closes at 5pm",
};

同样地,type TodoPreview = Omit<Todo, "description">等价于一下写法

type TodoPreview = {
  title: string;
  completed: boolean;
  createdAt: number;
}

ReturnType

构造一个由函数 Type 的返回类型组成的类型。

declare function f1(): { a: number; b: string };
 
type T0 = ReturnType<() => string>;
//相当于 type T0 = string

type T1 = ReturnType<(s: string) => void>;
//相当于 type T1 = void

type T2 = ReturnType<<T>() => T>;
//相当于 type T2 = unknown

type T3 = ReturnType<<T extends U, U extends number[]>() => T>;
//相当于 type T3 = number[]

type T4 = ReturnType<typeof f1>;
//相当于
/*
  type T4 = {
    a: number;
    b: string;
  }
*/

type T5 = ReturnType<any>;
//相当于 type T5 = any

type T6 = ReturnType<never>;
//相当于 type T6 = never

type T7 = ReturnType<string>; //警告: Type 'string' does not satisfy the constraint '(...args: any) => any'.

type T8 = ReturnType<Function>; 
//警告:
// Type 'Function' does not satisfy the constraint '(...args: any) => any'.
// Type 'Function' provides no match for the signature '(...args: any): any'.

有的朋友可能觉得有些多次一举了,可以看一下 XFlow 中的用到的地方

image.png

image.png 这样使用一个函数的好处是不是就体现出来了,因为作为一个函数,里面可以进行一些逻辑处理。就像 React 中的 useState,也支持通过通过两种方式定义初始值

const [count, setCount] = useState(0);

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});