typescript - Tvinga generiska och utökande typer att ha samma egenskaper


Jag har följande gränssnitt som tillåter att skapa ett objekt med vilken nyckel som helst så länge värdet är ett annat objekt som innehåller value och error-egenskaperna:

interface FormFields {
    [key: string]: {
        value: string;
        error: boolean;
    };
}

Då har jag följande funktion som baserat på objektet vi skickar in, skapar typvakt enligt de nycklar som finns i det objektet:

// Returns form fields object with type guard
const getFormFields = <T extends FormFields>(formFields: T) => formFields;

Det fungerar som förväntat om jag sätter vilken nyckel som helst och tilldelar som värde objektet som innehåller egenskaperna value och error:

// Works as expected if i pass all the necessary properties
const formFields2 = getFormFields({
    email: { value: '[email ', error: false },
});

console.log(formFields2.email);
console.log(formFields2.password); // Property 'password' does not exist on type '{ email: { value: string; error: false; }; }'.ts(2339)

Det misslyckas också som förväntat om jag utelämnar en av egenskaperna som det nästlade objektet bör innehålla:

// Fails as expected if i don't pass error property
const formFields = getFormFields({
    email: { value: '[email ' }, // Property 'error' is missing in type '{ value: string; }' but required in type '{ value: string; error: boolean; }'.ts(2741)
});

Men problemet uppstår när jag inom de inbäddade objekten lägger till någon annan egenskap än value eller error eftersom TypeScript kommer att acceptera det:

// But doesn't prevent of adding more properties
const formFields3 = getFormFields({
    email: {
        value: '[email ',
        error: false,

        // I want typescript yelling an error when attempting to add
        // any property other than value or error
        extraProperty: "extra property shouldn't be allowed",
    },
});

Jag förstår att när vi utökar en typ <T extends FormFields> säger vi inte till TypeScript att det generiska T-objektet kommer att vara samma som FormFields, utan vi säger att det åtminstone ska uppfylla FormFields-strukturen. Så jag undrade om det finns något sätt genom att ändra gränssnittet eller begränsningen att säkerställa att T innehåller objekt med value- och error-egenskaper och inte något annat.

Svar #1

Jag tror inte att du kan göra detta med en generisk typ, men jag ser ingen anledning till en generisk typ om du inte vill ha extra egenskaper; använd FormFields som typen för parametern:

const getFormFields = (formFields: FormFields) => formFields;

Det misslyckas på de ställen du sa att du ville att det skulle misslyckas, och fungerar med exakt value och error egenskaper — om du använder ett objektliteral som argument (som du gör i dina exempel). Det beror på att TypeScript gör ”excess property checks” på objektliteral som ett undantag från den allmänna regeln att en objekttyp-subtyp är tillåten där objektstypen förväntas.

Du kan behöva justera returtypen för funktionen. Du sa att den lägger till en typkontroll till objektet (jag antar att du utelämnade det i frågan för enkelhetens skull), så returtypen måste tillåta det.

Här är det tredje exemplet och det fel det nu orsakar:

// Prevents adding more properties
const formFields3 = getFormFields({
    email: {
        value: "[email ",
        error: false,

        // I want typescript yelling an error when attempting to add
        // any property other than value or error
        extraProperty: "extra property shouldn't be allowed",
//      ^^^^^^^^^^^^^−−−−−−−−
//      Type '{ value: string; error: false; extraProperty: string; }' is not
//      assignable to type '{ value: string; error: boolean; }'.
//      Object literal may only specify known properties, and 'extraProperty'
//      does not exist in type '{ value: string; error: boolean; }'.(2322)
    },
});

Lekplatslänk