简体   繁体   English

如何将 Typescript 函数重载与具有多个参数的函数一起使用?

[英]How to use Typescript Function Overloading with a function that has multiple parameters?

I've a function that tries to find a value in an array of numbers.我有一个函数试图在数字数组中找到一个值。 The value can be either an object or a number.该值可以是一个对象或一个数字。 If the value is an object, there's a 'key' property that is used to get the number value from the object.如果值是一个对象,则有一个“key”属性用于从对象中获取数字值。

I'm trying to use Function Overloading to have a single function that can handle both cases.我正在尝试使用Function Overloading来拥有一个可以处理这两种情况的函数。

type ObjectType = {
    [key: string]: number
}

type FunctionType = {
    <T extends ObjectType>(v: T, list: number[], key: string): T | undefined
    (v: number, list: number[]): number | undefined
}

const find: FunctionType = <T extends ObjectType | number,>(v: T, list: number[], key?: string)=>{

    const value = typeof v === 'number' ? v : v[key]

    return list.find((item)=>{
        return item === value
    })
}

This creates an error Type undefined cannot be used as an index type这会产生错误Type undefined cannot be used as an index type

But in this branch, the value of v is an object, so how is key not defined?但是在这个分支中,v的值是一个对象,那么key怎么没有定义呢?

TS-Playground TS-游乐场

Thank you谢谢

Edit编辑

A solution was proposed by Alex Wayne, but it doesn't properly narrow value to a number. Alex Wayne 提出了一个解决方案,但它没有正确地将值缩小到一个数字。

If instead of calling list.find, we called a custom function that only accepts numbers, typescript would throw an error如果我们不调用 list.find,而是调用一个只接受数字的自定义函数,typescript 会抛出错误


const findNumber = (v: number, list: number[])=>{
    /* Do stuff with v as a number */
    return list.indexOf(v)
}

const find: FunctionType = <T extends ObjectType | number>(v: T, list: number[], key?: keyof T)=>{
    const value = (key && typeof v === 'object') ? v[key] : v

    return list[findNumber(value, list)]
}

Type 'ObjectType' is not assignable to type 'number' “ObjectType”类型不能分配给“number”类型

TS-Playground TS-游乐场

The implementation function has no idea what its overloads are.实现函数不知道它的重载是什么。 This means that an overload function implementation must still be completely valid if the overload function signatures are removed.这意味着如果重载函数签名被删除,重载函数实现必须仍然完全有效。

So while it is true that if typeof v === 'number' then typeof key === 'string' the compiler doesn't know that.因此,虽然如果typeof v === 'number'然后typeof key === 'string'编译器不知道这一点是正确的。

If you look purely at this function signature:如果你纯粹看这个函数签名:

<T extends ObjectType | number,>(v: T, list: number[], key?: string)=>{

Then you see that find({ someKey: 1 }, [1,2,3]) is a valid invocation.然后你会看到find({ someKey: 1 }, [1,2,3])是一个有效的调用。 The overloads prevent that invocation, but again this function must be valid without those overloads.重载阻止了该调用,但该函数在没有这些重载的情况下也必须有效。


Which means to fix this you'll have to test for the presence of key before you use it.这意味着要解决此问题,您必须在使用之前测试key是否存在。

const value = (key && typeof v === 'number') ? v : v[key]

However, this gives a new problem:然而,这又带来了一个新问题:

Type 'undefined' cannot be used as an index type.(2538)

I believe this is because typeof v === 'number' is not a sufficient refinement.我相信这是因为typeof v === 'number'不够细化。 I'm not sure exactly what else it thinks it could be, but if you invert the ternary and instead refine by typeof v === 'object' typescript is happy.我不确定它认为它可能是什么,但如果你反转三元并通过typeof v === 'object'打字稿进行优化,那么它会很高兴。

const value = (key && typeof v === 'object') ? v[key] : v

Playground 操场


Lastly, you can probably improve type safety here by using key: keyof T instead.最后,您可以在这里使用key: keyof T来提高类型安全性。

const find: FunctionType = <T extends ObjectType | number>(v: T, list: number[], key?: keyof T)=>{
    const value = (key && typeof v === 'object') ? v[key] : v

    return list.find((item)=>{
        return item === value
    })
}

See playground 看游乐场

First of all, let's make sure that invalid state is unrepresentable.首先,让我们确保无效状态是不可表示的。 Let's define union types:让我们定义联合类型:

type IsObj<T> = {
    value: T, key: keyof T
}

type IsNumber = { value: number }

type Params<T> = IsObj<T> | IsNumber

As you might have noticed, I have binded 2nd and 3rd arguments.您可能已经注意到,我绑定了第二个和第三个参数。

Now, we can define our custom typeguard:现在,我们可以定义我们的自定义类型保护:

const isNumber = <T,>(params: Params<T>): params is IsNumber => typeof params.value === 'number'

Let's put it all together:让我们把它放在一起:


type ObjectType = {
    [key: string]: number
}

type IsObj<T> = {
    value: T, key: keyof T
}

type IsNumber = { value: number }

type Params<T> = IsObj<T> | IsNumber

const isNumber = <T,>(params: Params<T>): params is IsNumber => typeof params.value === 'number'

type FunctionType = {
    <T extends ObjectType>(list: number[], params: IsObj<T>): T | undefined
    (list: number[], params: IsNumber): number | undefined
}

const find: FunctionType = <T extends ObjectType | number,>(list: number[], params: Params<T>) => {

    const value = isNumber(params) ? params.value : params.value[params.key]

    return list.find((item) => item === value)
}

const result = find([1, 2, 3], { value: 42}) // ok
const result_ = find([1, 2, 3], { value: { age: 2 }, key: 'age' }) // ok

const result__ = find([1, 2, 3], { value: { age: 2 }, key: 'name' }) // expected error
const result___ = find([1, 2, 3], { value: 42, key: 'name' }) // expected error

Playground 操场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM