Typescript-gränssnitt för dynamiskt objekt


Anta att jag kan ha objekt som detta:

const filters = {
    name: { value: 'some-value', rank: 'some-value' },
    place: { value: 'some-value', population: 'some-value' },
}

och det skulle också kunna vara så här...

const filters = {
    name: { value: 'some-value', rank: 'some-value' },
    place: { value: 'some-value', population: 'some-value' },
    description: { value: 'some-value', translation: 'some-value' },
    popularity: { value: 'some-value', ranking: 'some-value' },
}

Hur skapar jag ett gränssnitt för det (ett dynamiskt objekt)?

Svar #1

En möjlighet skulle vara att göra description och popularity valfria genom att lägga till en ?, så här:

Tillvägagångssätt 1

interface Filters {
    name: object,
    place: object,
    description?: object,
    popularity?: object,
}

För mer information om egenskapsmodifierare, se dokumentationen

Ett annat tillvägagångssätt skulle vara att använda gränssnittsarv genom att använda nyckelordet extends

Tillvägagångssätt 2

interface ParentFilters {
    name: object,
    place: object
}

Och för det härledda gränssnittet

interface derivedFilters extends parentFilter {
   description: object,
   popularity: object,
}

Svar #2

Det är inte möjligt att skapa en typ som tvingar till att vara { a } eller { a, b, c }. Detta kallas Discriminerade unioner och du kan läsa mer om detta problem här.

Du kan använda båda tillvägagångssätten som @SimonRosengren nämnde. För att tvinga typ-säkerhet när du definierar ett objekt måste du implicit säga vilken typ som är vad:

interface FilterTypeBase {
    name: { value: string, rank: string },
    place: { value: string, population: string }
}

interface FilterTypeExtended extends FilterTypeBase {
    description: { value: string, translation: string },
    popularity: { value: string, ranking: string }
}

// 👍 OK
const filters: FilterTypeBase = {
    name: { value: 'some-value', rank: 'some-value' },
    place: { value: 'some-value', population: 'some-value' },
}

// ❌ ERROR: Property 'popularity' is missing
const filters2: FilterTypeExtended = {
    name: { value: 'some-value', rank: 'some-value' },
    place: { value: 'some-value', population: 'some-value' },
    description:  { value: "string", translation: "string" }
}

Om du vill bestämma i en funktion vilken av båda varianterna din typ är. Du kan göra detta med insnävning:

type FilterType = FilterTypeBase | FilterTypeExtended 

function doSomething(filter: FilterType) {
    if('description' in filter) {
        // Here it's the extended type
    }
}

Typescript-dokumentation: Avsmalning