Rozwiązanie z komentarzem
Opublikowano 23 lipca 2026Rozwiązanie
type ExtractParams<Path extends string> = Prettify< Path extends `${string}:${infer Param}/${infer Rest}` ? { [K in Param]: string } & ExtractParams<`/${Rest}`> : Path extends `${string}:${infer Param}` ? { [K in Param]: string } : {} >
Najczęstsza błędna odpowiedź
type ExtractParams<Path extends string> = Path extends `${string}:${infer Param}/${infer Rest}` ? { [K in Param]: string } & ExtractParams<`/${Rest}`> : Path extends `${string}:${infer Param}` ? { [K in Param]: string } : {}
Wszystko tu jest poprawne poza kształtem wyniku. Powstaje `{ id: string } & { postId: string }`, co da się przypisać do `{ id: string; postId: string }`, ale to nie ten sam typ — a testy sprawdzają dokładną równość. Owinięcie całości w `Prettify` spłaszcza przecięcie do jednego obiektu.
Linia po linii
`${string}:${infer Param}/${infer Rest}`Wiodące `${string}` pochłania wszystko przed dwukropkiem, więc wzorzec nie przejmuje się tym, że ścieżka zaczyna się od `/users`. `Param` wiąże się aż do następnego ukośnika; `Rest` bierze całą resztę.
ExtractParams<`/${Rest}`>Ukośnik jest dokładany z powrotem przed rekurencją. Bez niego końcowy segment w rodzaju `posts/:postId` wciąż pasowałby do pierwszego wzorca i rekurencja nie kończyłaby się czysto.
Path extends `${string}:${infer Param}`Drugi wzorzec nie ma końcowego ukośnika, więc pasuje tylko wtedy, gdy parametr jest ostatnim elementem łańcucha. To przypadek bazowy zatrzymujący rekurencję.
Prettify<...>Mapuje raz jeszcze po zebranych kluczach, tworząc świeży typ obiektowy. Końcówka `& {}` w `Prettify` zmusza TypeScript do faktycznego wyliczenia typu mapowanego zamiast trzymania go leniwie.
Do zapamiętania
Wzorce literałów szablonowych dopasowują się zachłannie od lewej. Gdy zbierasz typy obiektowe przez rekurencję, dostajesz przecięcie, a testy dokładnej równości je odrzucą — spłaszcz je przed zwróceniem.