是否可以根据函数参数生成静态类型对象?

问题描述 投票:0回答:1

我觉得这不可能,但我想先确定一下

我正在尝试根据函数中传递的字符串参数为 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

typescript
1个回答
0
投票

为了

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
的类型符合预期,它们与运行时发生的情况相匹配。

游乐场代码链接

© www.soinside.com 2019 - 2024. All rights reserved.