I'm new to Typescript, so probably not using the terminology right... bear with me

背景

我一直在try 扩展诸如string这样的原语,并取得了一定的成功.我甚至能够在我的扩展字符串中存储一些定制类型信息,如下所示:

// yes, we can extends strings!
type ExtendStr<P extends string = string, MaxLen extends number = 0> = P & { parent: P, options: { max: MaxLen } }

// create a LongStr with max length of 256
type LongStr = ExtendStr<string, 256>

我能够检索我存储在类型信息中的额外信息,如下所示:

// get the max length of a LongStr
type LongStrMax = LongStr['options']['max']
// yields 256... Hoorah! 

我甚至可以扩展LongStr并获得正确的工作类型加宽/缩小:

// make a ShortStr that extends LongStr but has max length 8
type ShortStr = ExtendStr<LongStr, 8>

// two example variables
let short: ShortStr = 'Hello' as ShortStr 
let long: LongStr = 'Omg this is a very long string!!' as LongStr 

// widening conversion is allowed as it should
long = short 

// narrowing conversion gives compiler error... sweet! 
short = long 

然而,这似乎"隐藏"了存储的信息...

现在的问题是

我要go 取ShortStr号车的'max'元.我在型号信息里看到了……

但当我试着拿到它时.

type ShortStrMax = ShortStr['options']['max']

..它的yield 率为never...is there a way???

操场

推荐答案

当你写作时

type ShortStr = ExtendStr<LongStr, 8>

long = short

it seems you'd like a ShortStr to be a subtype of LongStr. That is, every string of 8 or fewer characters is also a string of 256 or fewer characters. That makes sense. But your definition of ExtendStr<T, N> says that there will be an options.max property of type N. A ShortStr would therefore need to have an options.max property whose value is both 8 256. There's no value of this type, so this property type is equivalent to the never type 和 things start behaving strangely.


Conceptually it makes more sense to imagine the length property of the string. A value of ExtendStr<string, 256> should have a length property whose value is some non-negative whole number less than or equal to 256. You can represent this as a union type like 0 | 1 | 2 | ... | 254 | 255 | 256. And ExtendString<string, 8> should have a length property of type 0 | 1 | 2 | ... | 6 | 7 | 8. Now it's definitely possible for a string to have a length property of both those types, since the latter is strictly a subtype of the former. So if we can programmatically generate the right union type of numbers given N, we can write ExtendStr in terms of it.

这里有一种方法:

type LessThan<N extends number, A extends number[] = []> =
    N extends A['length'] ? A[number] : LessThan<N, [A['length'], ...A]>;

type LessThanOrEqual<N extends number> = N | LessThan<N>

The LessThan<N> type is a tail-recursive conditional type that turns a number literal type like 10 into a union of the nonnegative integers less than it by building up a tuple of these values 和 stopping when the tuple has length N. So 5 would become [0, 1, 2, 3, 4] which becomes 0 | 1 | 2 | 3 | 4.

LessThanOrEqual<N>就是NLessThan<N>的和,所以LessThanOrEqual<5>就是0 | 1 | 2 | 3 | 4 | 5.

现在是ExtendStr:

type ExtendStr<P extends string = string, MaxLen extends number = 0> =
    P &  { length: LessThanOrEqual<MaxLen> } 

Note that instead of creating phantom parentoptions.max properties, I just use the string type itself 和 the existing length property. You could keep it your way if you want, but I don't see much of a use for it in this example. It's up to you.

One more thing... you want to be able to extract the maximum length from the type. That is, given ExtendStr<string, N>, you'd like to retrieve N. Right now if you inspect the length property you get a big union, 和 you just want the maximum member of that union. Well, you can do that like this:

type Max<N extends number> = Exclude<N, LessThan<N>>

That works because LessThan<3 | 5> will be 0 | 1 | 2 | 3 | 4Exclude<3 | 5, 0 | 1 | 2 | 3 | 4> is 5.


所以,让我们试一试:

type LongStr = ExtendStr<string, 256>

type LongStrMax = Max<LongStr['length']>
/* type LongStrMax = 256 */

type ShortStr = ExtendStr<LongStr, 8>

let short: ShortStr = 'Hello' as ShortStr
let long: LongStr = 'Omg this is a very long string!!' as LongStr

long = short // okay
short = long // error

type ShortStrMax = Max<ShortStr['length']>;
//type ShortStrMax = 8

看上go 不错!


The above works well enough for me, but recursive conditional types can be tricky 和 sometimes cause compiler performance issues. If that happens you might want to revert to your current version 和 then deal with the problem some other way.

Playground link to code

Typescript相关问答推荐

为什么TypeScript失go 了类型推断,默认为any?''

打印脚本丢失函数的泛型上下文

更新为Angular 17后的可选链运算符&?&问题

为什么T[数字]不也返回UNDEFINED?

在分配给类型的只读变量中维护const的类型

为什么我的一个合成函数不能推断类型?

为什么我的查询钩子返回的结果与浏览器的网络选项卡中看到的结果不同?

从route.参数获取值.订阅提供";无法读取未定义";的属性

在TypeScript中扩展重载方法时出现问题

与toast.error一起react 中的Async TUNK错误消息

Angular NG8001错误.组件只能在一个页面上工作,而不是在她的页面上工作

如何使函数参数数组不相交?

如何将对象的字符串文字属性用作同一对象中的键类型

React Context TypeScript错误:属性';firstName';在类型';iCards|iSearch';

JSX.Element';不可分配给类型';ReactNode';React功能HOC

Typescript泛型-键入对象时保持推理

如何确定单元格中的块对 mat-h​​eader-cell 的粘附(粘性)?

const 使用条件类型时,通用类型参数不会推断为 const

具有来自其他类型的修改键名称的 TypeScript 通用类型

如何确定 Typescript 类型是否为文字