网站搜索

typescript - 如何穷尽地遍历字符串联合类型中的所有值?


我有一个这样的字符串联合类型:

export type Intervals = 'total' | 'weekly' | 'biweekly' | 'monthly' | 'annually';

我想通过循环遍历联合类型的值数组来向用户显示这些内容:

const intervals = ['total', 'weekly', 'biweekly', 'monthly', 'annually'];
intervals.forEach(...);

我如何为 intervals 数组设置类型,以确保它包含 Intervals 联合类型的所有值?

答案#1

不是从类型推导数组,而是从数组推导类型。

export const INTERVALS = ['total', 'weekly', 'biweekly', 'monthly', 'annually'] as const;

const temp = [...INTERVALS];
export type Interval = typeof temp[0];

答案#2

确保 intervals 必须可以赋值给 Array<Intervals> 足够简单:

const intervalsMisspelled: Array<Intervals> = 
  ['weekly', 'biweekly', 'annually', 'monthly', 'totul']; // error, "totul"

但那并不妨碍你省略某些内容:

const intervalsMissing: Array<Intervals> = 
  ['weekly', 'biweekly', 'annually', 'monthly']; // oops, no error but missing "total"

为了解决这个问题,你可以创建一个名为ensureArray()的辅助函数,它接受一个类型参数T(对你来说将是Intervals),然后返回一个新函数,该函数接受类型为T的参数列表,并推断该列表的数组类型A。如果A数组的元素(A[number])的类型比T更窄,那么你一定遗漏了什么,你应该会收到一个错误。以下是实现方法:

const ensureArray = <T>() => <A extends T[]>(
  ...a: A & ([T] extends [A[number]] ? A : never)
): A => a;

const ensureIntervalsArray = ensureArray<Intervals>();

const intervals = ensureIntervalsArray(
  'annually', 'biweekly', 'monthly', 'total', 'weekly'); // okay

const intervalsMisspelled = ensureIntervalsArray(
  'annually', 'biweekly', 'monthly', 'totul', 'weekly'); // error, "totul"

const intervalsMissing = ensureIntervalsArray(
  'annually', 'biweekly', 'monthly', 'weekly'); // error,
// [string, string, string, string] is not assignable to never

这可以使用,尽管你在 intervalsMissing 上遇到的错误相当晦涩,提示某些内容不可赋值给 never,但并没有真正告诉你问题出在哪里。由于 TypeScript 目前不允许我们自定义类型错误,我们只能尝试绕过这个问题。

以下给出了一个更奇怪的错误信息,但它给了开发者一个提示:

const ensureArray = <T>() => <A extends T[]>(
  ...a: A & ([T] extends [A[number]] ? A :
    { errorMessage: [Error, "You are missing", Exclude<T, A[number]>] })
): A => a;

const ensureIntervalsArray = ensureArray<Intervals>();

const intervalsMissing = ensureIntervalsArray(
  'annually', 'biweekly', 'monthly', 'weekly'); // error,
// Property 'errorMessage' is missing in type 
// '["annually", "biweekly", "monthly", "weekly"]' 
// but required in type '{ errorMessage: [Error, "You are missing", "total"]; }'

希望其中一个能够满足你的需求。祝你好运!