typescript 打字脚本开关Map

6rqinv9w  于 2023-01-27  发布在  TypeScript
关注(0)|答案(1)|浏览(135)

我有下面的TS代码,其中包含多个接口、组件和一个键/接口Map。

interface FooProps {
    'keyInFoo': string
}
const Foo = (props: FooProps) => {}

interface BarProps {
    'keyInBar': string
}
const Bar = (props: BarProps) => {}

interface PropsMap {
    'foo': FooProps,
    'bar': BarProps,
    //.. baz, etc...
}
type keyName = keyof PropsMap

const MyFunc = <T extends keyName>(key: T)  => {
    const props: PropsMap[T] = getProps(key)

    switch(key) {
        // We should know since T == typeof key === 'foo'
        // that typeof props === FooProps
        case 'foo': return Foo(props) // <- ERROR: Property ''keyInFoo'' is missing in type 'BarProps'
        case 'bar': return Bar(props) // <- ERROR Property ''keyInBar'' is missing in type 'FooProps'
    }
}

const getProps = <T extends keyName>(key: T): PropsMap[T] => {
    // ... some logic to get the props
    return {} as PropsMap[T]
}

我想对map的键运行switch/if-else语句,并让TypeScript知道每种情况下props的值。
当然,我可以在每种情况下Assertprops as FooProps等,但是我觉得应该有一种方法让TS推断类型。
我也尝试过使用泛型类型作为map,但没有取得额外的成功:

type PropsGeneric<T extends keyName> = 
    T extends 'foo' ? FooProps :
    T extends 'bar' ? BarProps : never;

见TSPlayground
我提出的一个可行但冗长的解决方案是为每个键创建一个类型保护,并在MyFunc中的一系列if-else if语句中使用它

const isFoo = <T extends keyName>(key: T, props: PropsMap[T]): props is FooProps => key === 'foo'
const isBar = <T extends keyName>(key: T, props: PropsMap[T]): props is BarProps => key === 'bar'

// ... etc
umuewwlo

umuewwlo1#

正如您已经注意到的,当涉及泛型时,要使TypeScript理解函数实现的类型是很棘手的。当两个都是泛型类型时,通过检查key的值来缩小props的类型是不起作用的。两个类型都不受编译器的影响。
我首先要修改的是PropsMap,让我们使用函数的类型作为值类型,这样我们就可以在以后使用ReturnTypeParameters实用程序类型。

interface PropsMap {
    'foo': typeof Foo,
    'bar': typeof Bar
}
type keyName = keyof PropsMap

const getProps = <T extends keyName>(key: T): Parameters<PropsMap[T]>[0] => {
    // ... some logic to get the props
    return {} as Parameters<PropsMap[T]>[0]
}

有几种方法可以编写条件语句,以便TypeScript正确地理解它们。它们大多类似于某种类型的查找对象,可以是强类型的。

const MyFunc = <T extends keyName>(key: T)  => {
    const props = getProps(key)

    const ret: { 
      [K in keyName]: (arg: Parameters<PropsMap[K]>[0]) => ReturnType<PropsMap[K]> 
    }[T] = {
        foo(props: FooProps) {
            return Foo(props)
        },
        bar(props: BarProps) {
            return Bar(props)
        }
    }[key]

    return ret(props)
}

我们名为ret的查找对象是用Map类型定义的。对于PropsMap中的每个键,ret也有一个对应的键,该键保存一个方法。每个方法都以props作为其参数。props参数和方法的返回类型严格地由Map类型定义。
剩下要做的就是用props调用ret
这给我们留下了一个非常冗长的实现,而且由于增加了函数调用,还带来了一些运行时开销,但这是一种无需类型Assert就可以实现强类型化的方法。
Playground

相关问题