TypeScript Type Challenges in Real Projects
2024 · Managing complex key combinations in TypeScript.
Background
In my previousdata visualization project, I have a type issue.
This project is about spectral analysis. It has many electrodes with different labels, such as electrode A, B, C.
You can write a TypeScript type to constrain these keys:
type ElectrodeKeys = 'A'|'B'|'C'
But these electrodes can be combined in pairs. such as electeode A-B, A-C, B-C.
Let's add more electrode like this below:
type ElectrodeKeys = 'A'|'B'|'C'|'A-B'|'A-C'|'B-C'
However, as the number of keys increase, this type becomes hard to maintain.
Solution
What if this question is in JavaScript world?
The first solution is to use a double loop:
function generatePairs(arr:string[]) {
const pairs = [];
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
pairs.push(`${ arr[i]}-${ arr[j]}`);
}
}
return pairs;
}
The second solution is to use recursive:
function generatePairs(arr:string[]) {
const pairs:string[] = []
function recursiveGenerate(currentIndex:number, currentPair:string[]) {
if (currentPair.length === 2) {
pairs.push(currentPair.join('-'));
return;
}
for (let i = currentIndex; i < arr.length; i++) {
recursiveGenerate(i + 1, [...currentPair, arr[i]]);
}
}
recursiveGenerate(0, []);
return pairs;
}
It's easy to understanding the idea, in this type issue, we can use TypeScript generics to achieve.
type type ElectrodeKeys = ["A", "B", "C"]
ElectrodeKeys = ['A','B','C']
type type CombKeys<T extends string[]> = T extends [infer F extends string, ...infer R extends string[]] ? `${F}-${R[number]}` | CombKeys<R> : never
CombKeys<function (type parameter) T in type CombKeys<T extends string[]>
T extends string[]> =
function (type parameter) T in type CombKeys<T extends string[]>
T extends [infer function (type parameter) F
F extends string, ...infer function (type parameter) R
R extends string[]]
? `${function (type parameter) F
F}-${function (type parameter) R
R[number]}` | type CombKeys<T extends string[]> = T extends [infer F extends string, ...infer R extends string[]] ? `${F}-${R[number]}` | CombKeys<R> : never
CombKeys<function (type parameter) R
R>
: never
type type CombElectrodeKeys = "A-B" | "A-C" | "B-C"
CombElectrodeKeys = type CombKeys<T extends string[]> = T extends [infer F extends string, ...infer R extends string[]] ? `${F}-${R[number]}` | CombKeys<R> : never
CombKeys<type ElectrodeKeys = ["A", "B", "C"]
ElectrodeKeys>
- T extends string[] means that the generic to accpet a string array.
- Further check if T is an array with at least on element. If so, it destructures the first element as F and the rest as R.
- CombKeys<R>: This is a recursive call to CombKeys to handle the remaining elements R.
- : never:if T is not an array , the result is never.