是否可以基于文字数组类型创建条件类型?
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
推断为无序数组类型,其中元素类型既不宽也不窄。在第一种情况下 T
是 Array<"search">
,在第二种情况下它是 UseTablePlugins[]
,两者都在元素类型中包含 "search"
。第三种情况T
是never[]
,第四种情况是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"
肯定存在或绝对不存在。所以你可能想要乐观的版本,只要你相当小心避免坏的边缘情况。
我想知道是否可以根据值是否在文字值数组中来创建条件类型:
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
推断为无序数组类型,其中元素类型既不宽也不窄。在第一种情况下 T
是 Array<"search">
,在第二种情况下它是 UseTablePlugins[]
,两者都在元素类型中包含 "search"
。第三种情况T
是never[]
,第四种情况是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"
肯定存在或绝对不存在。所以你可能想要乐观的版本,只要你相当小心避免坏的边缘情况。