Typescript泛型条件性部分属性,无需类型断言

huangapple go评论58阅读模式
英文:

Typescript generic conditional-partial property without type assertion

问题

I understand your request to provide a translation without addressing the specific code issue. Here's the translation of the content you provided:

我正在尝试创建一种类型,该类型具有取决于传递的泛型参数的可选属性。类似于以下内容:

在上面的示例中,我希望在V不是string时,getValue是必需的。在调用getPropsValue时,类型提示的工作方式是正确的。问题出在getPropsValuereturn value上,其中我得到了Type 'V' is not assignable to type 'string',尽管在它上面的条件中已经需要了它,但是这只在V不是string时才会发生。

我唯一能够使用的解决方法是将value断言为string
我还尝试了多次更改部分类型定义和使用联合类型,但不幸的是结果是一样的。
我尝试的另一种解决方案是检查value是否是string类型,如果不是,只需返回空字符串。虽然这在这种情况下是有效的,但在返回值是泛型的情况下是不可能的。
我将经常使用PartialWhenExtends,并且希望不必在每个地方都进行断言。
是否有一种方法可以编写部分类型,以便TypeScript可以正确推断类型?如果没有,是否有其他适用于这种情况的解决方案?

英文:

I am trying to create a type that has an optional property depending on the generic parameter passed. Something like:

type PartialWhen<Bool, T> = Bool extends true ? Partial<T> : T
type PartialWhenExtends<Base, Parent, T> = PartialWhen<Base extends Parent ? true : false, T>

type Props<V> = {
  value: V
} & PartialWhenExtends<V, string, {
  getValue: (item: V) => string
}>

function getPropsValue<V>({ value, getValue }: Props<V>): string {
  if (getValue !== undefined) {
    return getValue(value)
  }

  return value
}

const props1: Props<string> = {
  value: '123'
}
console.log(getPropsValue(props1))

const props2: Props<number> = {
  value: 123,
  getValue: (val) => val.toString()
}
console.log(getPropsValue(props2))

const props3: Props<string> = {
  value: 'abc',
  getValue: (val) => val.concat(val)
}
console.log(getPropsValue(props3))

In the example above, I want the getValue to be required when V is not string. When calling getPropsValue, the typehint works correctly. The problem is in the return value of getPropsValue in which I got Type 'V' is not assignable to type 'string', even though it is required when V is not string and would have returned in the condition above it.

The only solution I am able to use to solve this is by asserting the value as string.
I also tried changing the partial types definition multiple times & using union types, but unfortunately the result is the same.
Another solution that I tried is to check if the value is type of string, if not, just returns an empty string. While this works in this case, it is not possible where the return value is a generic.
I will use the PartialWhenExtends quite a lot, and would prefer to not do assertion everywhere.
Is there a way I can write the partial types so Typescript can correctly infer the type? If not, is there any other solution that can fit into this kind of use case?

答案1

得分: 0

Solution:

  1. 修复您的代码以正确处理 Props<string | number> 的情况
  2. 让您的代码明确检查字符串类型
type PartialWhen<Bool, T> = Bool extends true ? Partial<T> : T
type PartialWhenExtends<Base, Parent, T> = Base extends Parent ? Partial<T> : T

type Props<V> =
    { value: V } &
    PartialWhenExtends<[V], [string], { getValue: (item: V) => string }>
    
function getPropsValue<V>({ value, getValue }: Props<V>): string {
    if (getValue !== undefined) {
        return getValue(value)
    }
    if (typeof value === 'string')
        return value
    throw new Error()
}
// 正确
getPropsValue<string>({ value: 'asd' })
// ^?
getPropsValue<'asd'>({ value: 'asd' })
// ^?
getPropsValue<string & { a: 1 }>({ value: 'asd' as any })
// ^?

// 错误
getPropsValue<string | number>({ value: 123 })
// ^?
getPropsValue<number>({ value: 123 })
// ^?

您的函数可以像这样调用:

getPropsValue<string | number>({} as any)
// ^? 
function getPropsValue<string | number>({ value, getValue }: {
    value: string | number;
} & ({
    getValue: (item: string | number) => string;
} | Partial<{
    getValue: (item: string | number) => string;
}>)): string

所以 TypeScript 正确指出您的代码有问题。

我已经将代码修改为无需断言的工作方式,如下:

function getPropsValue<V>(
    data: V extends string
        ? { value: V, getValue?: undefined } | { value: V, getValue: (item: V) => string }
        : { value: V, getValue: (item: V) => string }
): string
getPropsValue<number | string>({} as any)
// ^?
// function getPropsValue<string | number>(data: {
//     value: string;
//     getValue?: undefined;
// } | {
//     value: string;
//     getValue: (item: string) => string;
// } | {
//     value: number;
//     getValue: (item: number) => string;
// }): string

但目前它会在失去非拆分 { value: V, getValue?: undefined } 的情况下停止工作。

英文:

Solution:

  1. Fix your code to be correct for Props<string | number> case
  2. Make your code explicitly check for a string
type PartialWhen<Bool, T> = Bool extends true ? Partial<T> : T
type PartialWhenExtends<Base, Parent, T> = Base extends Parent ? Partial<T> : T

type Props<V> =
    { value: V } &
    PartialWhenExtends<[V], [string], { getValue: (item: V) => string }>
    
function getPropsValue<V>({ value, getValue }: Props<V>): string {
    if (getValue !== undefined) {
        return getValue(value)
    }
    if (typeof value === 'string')
        return value
    throw new Error()
}
// ok
getPropsValue<string>({ value: 'asd' })
// ^?
getPropsValue<'asd'>({ value: 'asd' })
// ^?
getPropsValue<string & { a: 1 }>({ value: 'asd' as any })
// ^?

// errors
getPropsValue<string | number>({ value: 123 })
// ^?
getPropsValue<number>({ value: 123 })
// ^?

Your function can be called as

getPropsValue<string | number>({} as any)
// ^? 
function getPropsValue<string | number>({ value, getValue }: {
    value: string | number;
} & ({
    getValue: (item: string | number) => string;
} | Partial<{
    getValue: (item: string | number) => string;
}>)): string

so TS is correct about your code being wrong.

I've got the code working without assertion as

function getPropsValue<V>(
    data: V extends string
        ? { value: V, getValue?: undefined } | { value: V, getValue: (item: V) => string }
        : { value: V, getValue: (item: V) => string }
): string
getPropsValue<number | string>({} as any)
// ^?
// function getPropsValue<string | number>(data: {
//     value: string;
//     getValue?: undefined;
// } | {
//     value: string;
//     getValue: (item: string) => string;
// } | {
//     value: number;
//     getValue: (item: number) => string;
// }): string

but at the moment it loses non-split { value: V, getValue?: undefined } it stops working.

huangapple
  • 本文由 发表于 2023年4月19日 16:40:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/76052421.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定