javascript - TypeScript - Skicka dynamiskt egenskapsnamn till barn


Jag arbetar på ett routningsbibliotek med nästlad routning där jag försöker definiera en barnhanteringsfunktion som härleder föräldravägarna. Anledningen är att jag har en annan typ som kan härleda dynamiska vägparametrar från en sträng (t.ex. users/:id till { id: string }); så jag vill kunna skicka ner de härledda vägparametrarna från föräldrarutter till alla barnrutter i varje barns rutt-hanteringsfunktion.

Här är vad jag har hittills:

type RawRouterDefn<Namespace extends string = ""> = {
    [K: string]: Handler<Namespace, typeof K> | RawRouterDefn<`${Namespace}/${typeof K}`>;
};

type Handler<DataPath extends string, Path extends string> = ((args: { dataPath: DataPath, path: Path }) => unknown)

type Router<Routes extends RawRouterDefn, DataPath extends string = ""> = {
    [K in keyof Routes & string]: Routes[K] extends CallableFunction
        ? Handler<DataPath, K>
        : Router<Routes[K], `${DataPath}/${K}`>;
};

function createRouter<T extends RawRouterDefn>(rawDefn: T): Router<T, ""> {
    // Implementation not important right now
    return {} as unknown as Router<T, "">
}

const router = createRouter({
    some: {
        deeply: {
            nested: {
                /**
                 * Here, dataPath should be inferred as `some/deeply/nested`,
                 * path should be `route`
                 */
                route: ({ dataPath, path }) => {}
            },
        },
    },
});

const route = router.some.deeply.nested.route;
// Correctly inferred **after** function applied, I need it within the router definition
type Params = Parameters<typeof route>[0]

(Och länken till lekområdet)

Jag är nästan där, förutom att dataPath bör härledas som /some/deeply/nested och inte /$ {string}/$ {string}/$ {string} och path bör härledas som /route och inte /$ {string}. Detta antyder för mig att det är något jag saknar i typen RawRouterDefn.

Så det verkar som att jag kan begränsa strängens form på ett korrekt sätt, men inte längre med mitt nuvarande tillvägagångssätt.

Igen, jag behöver att det här härleds när det används som funktionsargument, inte efteråt.

Svar #1

Ganska nära, behövde bara göra lite magi med generikerna:

function createRouter<T extends RawRouterDefn, R extends Router<T, "">>(rawDefn: R): R {

Och nu fungerar det: https://tsplay.dev/NDRE1W

Du behöver en annan generisk för att tala om för TypeScript att det du får i funktionen är exakt vad du returnerar. Att ta bort T extends RawRouterDefn och använda R extends Router<RawRouterDefn, ""> bryter inferensen, så ja, gör inte det.