【问题标题】:How to index functions that return objects from a factory with possible additional properties如何索引从工厂返回具有可能附加属性的对象的函数
【发布时间】:2023-01-25 13:00:56
【问题描述】:

我正在尝试重构我的代码以抽象冗余部分以提高可维护性。我正在尝试创建一个函数,该函数将根据传递的参数执行不同的较小函数并返回必要的数据。

对于这些较小的函数,它们返回一个对象,该对象的属性来自工厂函数,加上我定义的任何其他属性,这些属性可能存在于所有较小的函数之间,也可能不存在。

const factoryFunc = () => ({
    func1: () => 'func1',
    func2: () => 'func2',
    func3: () => 'func3',
})

const extendedFuncs1 = () => ({
    ...factoryFunc(),
    additionalFunc1: () => 'additionalFunc1'
})

const extendedFuncs2 = () => ({
    ...factoryFunc()
})

我使用 ReturnType 实用程序提取了从这些函数返回的类型。我想获取每个可用函数的键,因此我创建了一个类型,将键映射到它们各自的函数名称。

type TExtendedFuncs1 = ReturnType<typeof extendedFuncs1>
type TExtendedFuncs2 = ReturnType<typeof extendedFuncs2>

type TFuncsTypes = {
    extendedFuncs1: keyof TExtendedFuncs1;
    extendedFuncs2: keyof TExtendedFuncs2;
}

然后,我创建了一个条件类型来检查该属性是否属于某个函数,如果是,则提供该函数可用的键。 OtherType 仅供参考。

type TOtherTypes = {
    otherType1: string;
    otherType2: number
}

type Conditional<T = keyof (TOtherTypes & TFuncsTypes)> = T extends keyof TFuncsTypes ? {
    name: T;
    objKey: TFuncsTypes[T]
} : never

有了这个,我希望 objKey 属性成为 TFuncsTypes['extendedFuncs1']TFuncsTypes['extendedFuncs2'] 返回对象的键。此外,如果它是TFuncsTypes['extendedFuncs1'],则additionalFunc1 属性应该存在。

const testFunc = (data: Conditional[]) => {
    const findData = (key: string) => data.find((d) => d.name === key);

    const res1 = extendedFuncs1()[findData('extendedFuncs1')!.objKey]
    const res2 = extendedFuncs2()[findData('extendedFuncs2')!.objKey]

    return {res1, res2}
}

但是,打字稿给我一个res2的错误

Property 'additionalFunc1' does not exist on type '{ func1: () => string; func2: () => string; func3: () => string; }'

我知道它不存在,因为它是在工厂外定义的附加属性,但为什么它没有根据 TFuncsTypes['extendedFuncs2'] 中定义的键进行评估?

这是我制作的playground

【问题讨论】:

  • 是的@jcalz,这解决了这个问题。非常感谢您的分享。
  • 还有一件事,如果我在另一个函数中使用 findData 函数怎么办?我是否也必须使该功能通用?
  • 或许?这取决于具体情况,并且可能超出此问题及其评论部分的范围。

标签: javascript typescript


【解决方案1】:

当您编写 Conditional[] 类型时,您指的是 generic Conditional 类型的数组,其中类型参数采用它们的 defaults

type C = Conditional;
/* type C = {
    name: "extendedFuncs1";
    objKey: "additionalFunc1" | "func1" | "func2" | "func3";
} | {
    name: "extendedFuncs2";
    objKey: "func1" | "func2" | "func3";
} */

因此,这是一个 union 类型。因此,无论 key 是什么,您的 findData 函数都会返回该联合类型(或 undefined)的值:

const findData = (key: string) => data.find((d) => d.name === key);

const hmm = findData("extendedFuncs2");
/* const hmm: {
name: "extendedFuncs1";
objKey: "additionalFunc1" | "func1" | "func2" | "func3";
} | {
name: "extendedFuncs2";
objKey: "func1" | "func2" | "func3";
} | undefined */

就编译器所知,结果将具有 "additionalFunc1" 类型的 objKey 属性。这意味着如果您尝试索引 extendedFuncs2() 的输出,它会报错。


为了解决这个问题,您需要表达传递给findData()key 字符串与可能的输出类型之间的关系,以便编译器理解,例如findData("extendedFuncs2")?.objKey 不能是"additionalFunc1"

一种方法是在 keyK 类型中使 findData() 通用,并使用 call signature for the find() method of arrays 如果它的回调是 custom type guard function,则返回更窄的结果:

interface Array<T> {
    // This is the call signature we want to use
    find<S extends T>(
        predicate: (this: void, value: T, index: number, obj: T[]) => value is S,
        thisArg?: any
    ): S | undefined;
}

像这样:

const findData = <K extends string>(key: K) => data.find(
    (d): d is Extract<typeof d, { name: K }> => d.name === key
);

现在当我们调用findData()时,我们将看到更具体的结果:

const hmm = findData("extendedFuncs2");
/* const hmm: {
name: "extendedFuncs2";
objKey: "func1" | "func2" | "func3";
} | undefined */

编译器现在知道,如果 objKey 存在,它将是 extendedFuncs2() 结果的有效键,并且您的错误消失了:

const res2 = extendedFuncs2()[findData('extendedFuncs2')!.objKey]; // okay

根据您的用例,这可能就足够了。


Playground link to code

【讨论】:

    猜你喜欢
    • 2019-10-18
    • 2020-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-22
    相关资源
    最近更新 更多