当你写作时
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>
就是N
和LessThan<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 parent
和 options.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 | 4
和 Exclude<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个