是否可以基于文字数组类型创建条件类型?

Is it possible to create a conditional type based on a literal array type?

我想知道是否可以根据值是否在文字值数组中来创建条件类型:

export type UseTablePlugins = "search" | "pagination";

function useTable<Plugins extends UseTablePlugins[]>(plugins: Plugins) {
  ...
  
  // This is the part I'm stuck on
  return searchState: Plugins extend "search"[] ? [string, Dispatch] : never;
}

我知道 TypeScript 在运行时会消失,因此无法找到变量的值,但我想知道如果我做类似 useTable(["search"]) 的事情,TypeScript 是否会理解 search 在方式?我希望我说得有道理 :).

编辑:这是 TS 操场上的 minimal example

如果您有一个数组类型 T extends UseTablePlugins[] 并且您希望另一个类型计算为 SearchState 如果 "search" 出现在 T 的元素类型中并且 null 否则,你可能想写这样的类型:

"search" extends T[number] ? SearchState : null;

类型 T[number]T 的元素类型,因为它是你在 index into an array of type T with an index of type number. Generally that will give you the union of the types of each known element type. So if T is the tuple type ["search", "pagination"] 时得到的类型,那么 T[number]"search" | "pagination".

请注意,当您使用 AAA extends BBB ? true : false 形式的(非 distributive) conditional type 时,您将获得 true 当且仅当您可以分配 AAA 到类型 BBB 的变量。你可以将类型 "search" 的值分配给类型 "search" | "pagination" 的变量(扩大是安全的)但反之则不然(这是不安全的)缩小)。所以你需要 "search" extends T[number] 而不是 T[number] extends "search"T extends "search"[],这更像是检查 only "search" 是否出现在数组中。


那么 useTable() 看起来像:

function useTable<T extends UseTablePlugins[]>(plugins: T) {
  const searchable = plugins.includes("search");
  const searchState = searchable ? useState("") : null;
  return searchState as "search" extends T[number] ? SearchState : null;
}

让我们看看它是否按您想要的方式工作。这将在很大程度上取决于编译器是否推断 T 足够狭窄以考虑 plugins 参数的所有唯一元素。这里有一些快乐的案例:

const okay1 = useTable(["search"]); // SearchState
const okay2 = useTable(["search", "pagination"]); // SearchState
const null1 = useTable([]); // null
const null2 = useTable(["pagination"]); // null

在所有这些情况下,编译器将 T 推断为无序数组类型,其中元素类型既不宽也不窄。在第一种情况下 TArray<"search">,在第二种情况下它是 UseTablePlugins[],两者都在元素类型中包含 "search"。第三种情况Tnever[],第四种情况是Array<"pagination">,两者的元素类型都不包含"search"

不幸的是,边缘情况仍然存在问题:

const oops1 = useTable([Math.random() < 0.99 ? "pagination" : "search"]);
// SearchState, but 99% chance it should be null at runtime
const oops2 = useTable((["search", "pagination"] as const).slice(1))
// SearchState, but definitely null at runtime

在这两种情况下,T 都被推断为 UseTablePlugins[]。编译器知道 plugins 的元素是 "pagination""search" 但它不知道 "search" 可能或肯定会不存在。这对编译器来说是合理的行为,但它会导致 useTable() 的 return 类型成为 SearchState 而不是 null,这是不好的。这些极端情况的发生是因为实施在某种意义上过于“乐观”,并假设如果 "search" 可能 存在,那么它 现在。

一般来说,编译器无法准确知道每个数组中会有哪些值,因此您要么过于乐观,要么过于悲观。如果您过于悲观,类型永远不会 错误 ,但它可能太宽而无用。这或多或少是 useTable() returns SearchState | null 不管你传入什么,或者至少你得到 SearchState | null 的情况,除非编译器肯定 "search" 肯定存在或绝对不存在。所以你可能想要乐观的版本,只要你相当小心避免坏的边缘情况。

Playground link to code