我觉得这不可能,但我想先确定一下
我正在尝试根据函数中传递的字符串参数为 Intellisense 创建一个静态类型的参数对象:
type Query = {
_def: {
params: Record<string, string> // what to put here?
}
params: (name: string) => Query;
}
const queryBuilder = (def: Query["_def"]): Query => {
return {
_def: def,
params: (name) => {
const innerDef = {
...def,
params: {
...def.params,
[name]: undefined as unknown as string // here just to save the type
}
}
return queryBuilder(innerDef);
}
}
}
const c = queryBuilder({} as Query["_def"]);
const d = c.params("hello").params("world");
d._def.params; // should Intellisense hello and world
const e: typeof c._def.params = {} // should throw error saying hello and world are required
Record<string, string>
不提供 Intellisense 或错误。我不能做常量断言,因为没有什么可断言的。还有什么我没有想到的吗?
编辑:Playground
为了
Query
跟踪其 _def.params
属性中的键,它应该是 generic 在类型参数 K
对应于这些键的 union。它可能看起来像这样:
type Query<K extends string = never> = {
_def: {
params: Record<K, string>
}
params: <L extends string>(name: L) => Query<K | L>;
}
因此,
Query<K>
具有类型为 def.params
的 Record<K, string>
属性,使用 Record<K, V>
实用程序类型 来表示在 string
中的键处具有 K
值属性的对象类型。请注意,我已将 K
default 设置为 never
类型,可以将其视为“空联合”。因此,没有类型参数的 Query
与 Query<never>
相同,它具有没有任何已知键的 _def.params
属性。
注意
params
属性本身就是一个泛型函数;它在 L
中是通用的,即 name
参数的类型,返回值是 Query<K | L>
类型,这意味着它是一个 Query
具有您开始使用的 K
键和 L
您刚刚添加的密钥。
现在我们应该给
queryBuilder
打字。在接下来的内容中,我更关心保证 callers 的类型安全,而我在 implementation 中使用一些 type assertions 以放松我知道很好但编译器没有的东西:
const queryBuilder = <K extends string = never>(
def: { params?: Record<K, string> } = {}
): Query<K> => {
return {
_def: { params: {} as any, ...def },
params: (name) => {
const innerDef = {
...def,
params: {
...def.params,
[name]: "someString"
}
}
return queryBuilder(innerDef) as any;
}
}
}
所以
queryBuilder
是类型
declare const queryBuilder: <K extends string = never>(def?: {
params?: Record<K, string> | undefined;
}) => Query<K>;
这意味着它接受一个 optional
def
参数,它是一个具有 optional params
通用类型 Record<K, string>
属性的对象。而K
默认为never
。它返回一个Query<K>
类型的值。因此,如果您调用 queryBuilder()
或 queryBuilder({})
,它应该产生一个 Query
。但是如果你调用queryBuilder({foo: "abc", bar: "def"})
,那么它应该产生一个Query<"foo" | "bar">
。
让我们试试看:
const c = queryBuilder({});
// const c: Query<never>
const d = c.params("hello").params("world");
// const d: Query<"hello" | "world">
console.log(d._def.params.hello.toUpperCase()) // "SOMESTRING"
const e = queryBuilder({ params: { foo: "abc", bar: "def" } }).params("baz");
// const e: Query<"foo" | "bar" | "baz">
console.log(e._def.params.bar.toUpperCase()) // "DEF"
看起来不错。
c
、d
和 e
的类型符合预期,它们与运行时发生的情况相匹配。