1-10 个 TypeScript 项目的自定义实用程序类型

2025-06-11

1-10 个 TypeScript 项目的自定义实用程序类型

在 TypeScript 开发的动态环境中,实用程序类型是构建适应性强、清晰且健壮的类型结构的基础工具。本文介绍了 10 种广泛使用的实用程序类型,它们可以解决常见的编码挑战,从操作原始类型到微调对象属性,从而全面控制不可变性和可选性。

第二部分:11-20 TypeScript 项目的自定义实用程序类型

目录

原始

类型Primitive表示 JavaScript(TypeScript)中所有基本数据类型的集合。Primitive 对于需要处理一系列简单数据类型的函数或变量非常有用。

type Primitive = string | number | bigint | boolean | symbol | 
    null | undefined;
Enter fullscreen mode Exit fullscreen mode

例子

下面的例子演示了过滤器如何有效地适应各种原始数据类型。

interface Product {
  id: symbol; // Unique identifier
  name: string;
  price: number;
  available: boolean;
  totalSales: bigint; // Large number representing total sales
}

// The filter definition
interface FilterDefinition {
  key: keyof Product;
  value: Primitive;
}

// Function to filter products
function filterProducts(
  products: Product[],
  filters: FilterDefinition[]
): Product[] {
  return products.filter((product) => {
    return filters.every((filter) => {
      return product[filter.key] === filter.value;
    });
  });
}
Enter fullscreen mode Exit fullscreen mode

假的

该类型Falsy涵盖了 JavaScript(TypeScript)认为所有可能为“falsy”的值。在 JavaScript 中,如果一个值false在布尔上下文(例如,在if语句中)中被转换为布尔值,则该值被视为 falsy。此类型是针对跨不同原始类型强制转换为布尔值的场景而设计的。

type Falsy = false | "" | 0 | 0n | null | undefined;
Enter fullscreen mode Exit fullscreen mode

示例
例如,考虑一个表单字段,它可能接受几个虚假值,包括null、、和空字符串false0

// A utility function that returns a default value if the
// input is Falsy
function getDefaultIfFalsy<T>(
  value: T | Falsy, 
  defaultValue: T
): T {
  return value || defaultValue;
}

// Define form data interface
interface FormData {
  name: string;
  email: string;
  age: number;
  billingAddress: string;
  shippingAddress?: string;
  sameAsBillingAddress: boolean;
}

// Use `getDefaultIfFalsy` for `shippingAddress`
formData.shippingAddress = 
  getDefaultIfFalsy(formData.shippingAddress, "");
Enter fullscreen mode Exit fullscreen mode

真理

这种构造可Truthy<T>用于从类型联合中过滤掉假值,仅保留 JavaScript 中被视为真实的类型。

type Truthy<T> = T extends Falsy ? never : T;
Enter fullscreen mode Exit fullscreen mode

它在实践中是如何运作的:

// Result: 1 | {}
type Example = Truthy<"" | 1 | false | {} | undefined>;
Enter fullscreen mode Exit fullscreen mode

例子

利用该Truthy类型,您可以创建一个函数,该函数以具有可选属性的对象作为输入,并返回仅具有已填写的属性(真值)且完全类型的对象。

function processInput<T extends object>(
  obj: T
): {[K in keyof T]: Truthy<T[K]>} {
  const result: Partial<{[K in keyof T]: Truthy<T[K]>}> = {};
  Object.entries(obj).forEach(([key, value]) => {
    if (value) {
      result[key as keyof T] = value as any;
    }
  });
  return result as {[K in keyof T]: Truthy<T[K]>};
}

const userInput = {
  name: "John",
  age: 0,
  email: ""
};

const processedInput = processInput(userInput);
console.log(processedInput); // Output: { name: "John" }
Enter fullscreen mode Exit fullscreen mode

空值

Nullish类型表示值缺失或变量未初始化。其主要目的是处理可选属性、变量或函数参数,这些参数可能并非始终具有值。它允许区分缺失值和存在但带有假值(例如0false或空字符串)的值。因此,使用显式处理这些或 的情况Nullish有助于增强代码的可靠性nullundefined

type Nullish = null | undefined;
Enter fullscreen mode Exit fullscreen mode

例子

在示例中,Nullish用于管理可选UserInfo属性,允许函数getFoodRecommendations在未提供这些属性时默认使用特定值。这种方法确保函数能够处理属性为null或 的情况undefined,从而避免直接访问未设置或可选属性可能引发的潜在错误。

interface UserInfo {
  name: string;
  favoriteFood?: string | Nullish;
  dietRestrictions?: string | Nullish;
}

function getFoodRecommendations(user: UserInfo) {
  const favoriteFood = user.favoriteFood ?? 'Generic';
  const dietRestriction = user.dietRestrictions ?? 'No Special Diet';

  // In a real app, there could be a complex logic to get proper food recommendations.
  // Here, just return a simple string for demonstration.
  return `Recommendations: ${favoriteFood}, ${dietRestriction}`;
}
Enter fullscreen mode Exit fullscreen mode

非空键

类型NonNullableKeys构造用于过滤与可空(即nullundefined)值关联的对象类型的键。这种实用类型在需要确保仅访问对象中那些保证为非null和非 的属性的场景中特别有用undefined。例如,它可以应用于需要严格类型安全且未经显式检查就无法操作可空属性的函数中。

type NonNullableKeys<T> = { 
    [K in keyof T]: T[K] extends Nullish ? never : K 
}[keyof T];
Enter fullscreen mode Exit fullscreen mode

例子

在下面的示例中,我们引入了一个UserProfile具有各种属性的接口。NonNullableKeys我们使用 实现了一个prepareProfileUpdate函数。此函数从用户个人资料更新对象中过滤掉可空属性,确保更新负载中只包含有意义(非null/ undefined)值的属性。这种方法在 API 交互中尤其有用,因为为了维护后端系统的数据完整性,需要避免null/undefined数据提交。

interface UserProfile {
  id: string;
  name: string | null;
  email?: string | null;
  bio?: string;
  lastLogin: Date | null;
}

function prepareProfileUpdate<T extends object>(
  profile: T
): Pick<T, NonNullableKeys<T>> {
  const updatePayload: Partial<T> = {};
  (Object.keys(profile) as Array<keyof T>).forEach(key => {
    const isValuePresent = profile[key] !== null &&
                           profile[key] !== undefined;
    if (isValuePresent) {
      updatePayload[key] = profile[key];
    }
  });
  return updatePayload as Pick<T, NonNullableKeys<T>>;
}

const userProfileUpdate: UserProfile = {
  id: "123",
  name: "John Doe",
  email: null,
  bio: "Software Developer",
  lastLogin: null,
};

const validProfileUpdate = prepareProfileUpdate(
  userProfileUpdate
);
// Output:
// { id: "123", name: "John Doe", bio: "Software Developer" }
console.log(validProfileUpdate);
Enter fullscreen mode Exit fullscreen mode

JSON对象

JSONObject类型可用于定义对象的形状,使其能够无损地转换为 JSON 或从 JSON 转换为 JSON,或用于与使用 JSON 通信的 API 交互。它采用一种称为递归类型或相互递归的构造,其中两个或多个类型相互依赖。递归类型可用于描述可以嵌套到任意深度的数据结构。

type JSONObject = { [key: string]: JSONValue };
type JSONValue = string | number | boolean | null | JSONObject | 
  JSONValue[];
Enter fullscreen mode Exit fullscreen mode

示例
在此示例中,我们为应用程序定义了一个配置对象。此配置对象必须可序列化为 JSON,因此我们强制其形状符合该JSONObject类型。

function saveConfiguration(config: JSONObject) {
  const serializedConfig = JSON.stringify(config);
  // In a real application, this string could be saved to a file,
  // sent to a server, etc.
  console.log(`Configuration saved: ${serializedConfig}`);
}

const appConfig: JSONObject = {
  user: {
    name: "John Doe",
    preferences: {
      theme: "dark",
      notifications: true,
    },
  },
  version: 1,
  debug: false,
};

saveConfiguration(appConfig);
Enter fullscreen mode Exit fullscreen mode

可选的除外

OptionalExceptFor类型是一种实用类型,它接受一个对象类型T和一组TRequiredKeys来自 的键T,使得除指定键之外的所有属性均为可选属性。这在对象大多数属性为可选属性,但少数属性为必需属性的场景下非常有用。此类型有助于更灵活地对对象进行类型化,而无需为可选属性的变体创建多个接口或类型,尤其是在仅要求存在部分属性的配置中。

type OptionalExceptFor<T, TRequiredKeys extends keyof T> = 
    Partial<T> & Pick<T, TRequiredKeys>;
Enter fullscreen mode Exit fullscreen mode

例子

在以下示例中,该OptionalExceptFor类型用于定义用户设置的接口,其中只有 是userId必需的,其他所有属性都是可选的。这允许更灵活地创建对象,同时确保userId始终必须提供 属性。

interface UserSettings {
  userId: number;
  notifications: boolean;
  theme: string;
  language: string;
}

type SettingsWithMandatoryID =
  OptionalExceptFor<UserSettings, 'userId'>;

const userSettings: SettingsWithMandatoryID = {
  userId: 123,
  // Optional: 'notifications', 'theme', 'language'
  theme: 'dark',
};

function configureSettings(
  settings: SettingsWithMandatoryID
) {
  // Configure user settings logic
}

configureSettings(userSettings);
Enter fullscreen mode Exit fullscreen mode

只读深度

ReadonlyDeep类型是一种实用程序,它使给定类型的所有属性T深度变为只读。这意味着不仅对象的顶级属性变为不可变,而且所有嵌套属性也递归地标记为只读。此类型在不可变性至关重要的场景中特别有用,例如在 Redux 状态管理中,防止意外的状态突变至关重要。

type ReadonlyDeep<T> = {
  readonly [P in keyof T]: T[P] extends object ? 
  ReadonlyDeep<T[P]> : T[P];
};
Enter fullscreen mode Exit fullscreen mode

示例
下面的示例确保Person对象本身及其任何嵌套属性都不能被修改,从而演示了如何ReadonlyDeep应用类型来确保深度不变性。

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

interface Person {
  name: string;
  age: number;
  address: Address;
}

const person: ReadonlyDeep<Person> = {
  name: "Anton Zamay",
  age: 25,
  address: {
    street: "Secret Street 123",
    city: "Berlin",
  },
};

// Error: Cannot assign to 'name' because it is a read-only
// property.
person.name = "Antonio Zamay";
// Error: Cannot assign to 'city' because it is a read-only
// property.
person.address.city = "San Francisco";
Enter fullscreen mode Exit fullscreen mode

部分深度

PartialDeep类型以递归方式将对象类型的所有属性T深度设为可选。这种类型在处理复杂嵌套对象并需要部分更新或指定它们的情况下特别有用。例如,在处理大型数据结构中的状态更新时无需指定每个嵌套字段,或者在定义可在多个级别覆盖默认值的配置时。

type PartialDeep<T> = {
   [P in keyof T]?: T[P] extends object ? PartialDeep<T[P]> : 
   T[P];
};
Enter fullscreen mode Exit fullscreen mode

例子

在下面的示例中,我们使用该PartialDeep类型来定义一个函数updateUserProfile,该函数可以接受对用户配置文件的部分更新,包括对address和等嵌套对象的更新preferences

interface UserProfile {
  username: string;
  age: number;
  address: {
    street: string;
    city: string;
  };
  preferences: {
    newsletter: boolean;
  };
}

function updateUserProfile(
  user: UserProfile,
  updates: PartialDeep<UserProfile>
): UserProfile {
  // Implementation for merging updates into user's profile
  return { ...user, ...updates };
}

const currentUser: UserProfile = {
  username: 'johndoe',
  age: 30,
  address: {
    street: '123 Elm St',
    city: 'Anytown',
  },
  preferences: {
    newsletter: true,
  },
};

const userProfileUpdates: PartialDeep<UserProfile> = {
  address: {
    city: 'New City',
  },
};

const updatedProfile = updateUserProfile(
  currentUser,
  userProfileUpdates
);
Enter fullscreen mode Exit fullscreen mode

品牌

Brand类型是一个 TypeScript 实用程序,它对其他结构相同的类型使用名义类型。TypeScript 的类型系统是结构化的,这意味着如果两个对象具有相同的形状,则无论其声明的名称或位置如何,它们都被视为同一类型。然而,在某些情况下,将两个形状相同的对象视为不同的类型是有益的,例如区分本质上相同但用途不同的类型(例如,用户 ID 和订单 ID 都是字符串,但代表不同的概念)。该Brand类型的工作原理是将一个类型T与一个唯一的品牌对象相交,从而有效地区分其他相同的类型,而无需更改运行时行为。

type Brand<T, B> = T & { __brand: B };
Enter fullscreen mode Exit fullscreen mode

例子

想象一下,一个应用程序的用户 ID 和订单 ID 都以字符串形式表示。如果没有品牌标识,这些 ID 可能会混淆,从而导致潜在的错误。使用Brand类型,我们可以创建两种不同的类型UserIdOrderId,这样就不会错误地将一个类型赋值给另一个类型。

type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;

function fetchUserById(id: UserId) {
  // Fetch user logic here
}

function fetchOrderByOrderId(id: OrderId) {
  // Fetch order logic here
}

// Creation of branded types
const userId: UserId = '12345' as UserId;
const orderId: OrderId = '67890' as OrderId;

// These calls are safe and clear
fetchUserById(userId); 
fetchOrderByOrderId(orderId);

// Error: Argument of type 'OrderId' is not assignable
// to parameter of type 'UserId'
fetchUserById(orderId);
Enter fullscreen mode Exit fullscreen mode
鏂囩珷鏉ユ簮锛�https://dev.to/antonzo/10-sustom-utility-types-for-typescript-projects-48pe
PREV
Redux 完整指南
NEXT
开发人员在测试时面临的 12 个问题及其解决方法