Zoeken op website

javascript - Hoe kan je dynamisch ervoor zorgen dat een functie in een object alleen wordt aangeroepen met de parameters die het accepteert?


Overweeg het volgende,

interface ITemplateA {
    name: string;
}
const templateA = ({ name }: ITemplateA) => `Hello, I am ${name}`

interface ITemplateB {
    age: number;
}
const templateB = ({ age }: ITemplateB) => `I am, ${age} years old`;

const templates = {
    templateA,
    templateB
}

interface IGenerateText {
    template: keyof typeof templates;
    params: any;
}
const generateText = ({ template, params }: IGenerateText) => templates[template](params);

Hoe kan ik het gedeelte met params: any refactoren, zodat TypeScript het volgende zou oppikken:

generateText({ template: 'templateA', params: { name: 'michael' } }); // no error
generateText({ template: 'templateA', params: { age: 5 } }); // error
generateText({ template: 'templateB', params: { age: 5 } }); // no error

Antwoord #1

Dit lijkt te werken. Ik denk dat je de as any binnen de functie ook met wat slimmigheid kunt weghalen...

const templateA = ({ name }: { name: string }) => `Hello, I am ${name}`;

const templateB = ({ age }: { age: number }) => `I am, ${age} years old`;

const templates = {
  templateA,
  templateB,
};
type TemplateName = keyof typeof templates;

interface IGenerateText<T extends TemplateName> {
  template: T;
  params: Parameters<typeof templates[T]>[0];
}

function generateText<T extends TemplateName>({ template, params }: IGenerateText<T>) {
  return (templates[template] as any)(params);
}

generateText({ template: "templateA", params: { name: "michael" } }); // no error
generateText({ template: "templateA", params: { age: 5 } }); // error
generateText({ template: "templateB", params: { age: 5 } }); // no error

Antwoord #2

Extern (d.w.z. bij het aanroepen van de functie) is het voldoende om een gemapt type te hebben (zoals typeof templates in jouw geval, waarbij templates een map/woordenboek is) en een afgeleid generiek typeargument dat een sleutel van het gemapte type selecteert, om de correlatie tussen enkele van de functiewaarden uit te drukken, zoals gedaan in het antwoord van @AKX.

Maar intern (d.w.z. binnen de functielichaam), is dit gemapte type niet genoeg: TypeScript statische analyse heeft geen manier om te weten dat slechts 1 sleuteldtype wordt gebruikt (het kan nog steeds een unie van sleutels zijn), daarom valt het terug op het behouden van de unie van alle types.

De enige manier om TS te laten weten dat slechts 1 sleutelwaarde aanwezig is, is door enige typevernauwing te gebruiken. En om TS de types van gerelateerde parameters mee te laten vernauwen, moeten ze onderdeel zijn van een gediscrimineerde unie. Helaas kan dit in jouw geval leiden tot wat herhalende code, wat momenteel niet kan worden vermeden om volledige veiligheid te behouden:

type Template = keyof typeof templates;

// Build a discrimated union from the templates map:
type DiscriminatedUnionTemplates = {
    //^? { template: "templateA"; params: ITemplateA; } | { template: "templateB"; params: ITemplateB; }
    // Use a mapped type:
    [T in Template]: {
        template: T;
        params: Parameters<typeof templates[T]>[0]
    }
}[Template] // Use indexed access to convert the mapped type into a union

const generateText = <T extends DiscriminatedUnionTemplates>({ template, params }: T) => {
    // Narrow the type within the union,
    // based on the `template` discriminant key
    switch (template) {
        case 'templateA': return templates[template](params); // Okay
        case 'templateB': return templates[template](params); // Okay, repetitive but currently only way to keep full safety
    }
};

generateText({ template: 'templateA', params: { name: 'michael' } }); // Okay
generateText({ template: 'templateA', params: { age: 5 } }); // Error: Object literal may only specify known properties, and 'age' does not exist in type 'ITemplateA'.
generateText({ template: 'templateB', params: { age: 5 } }); // Okay

Speeltuin Link

In uw geval is het duidelijk wat de functie intern doet, en we kunnen de type-aanduiding accepteren om de herhalende code te vermijden.

Gerelateerde artikelen