Typescript-funktion med ett argument som får sina nycklar från ett annat argument


Jag vill skapa en funktion som accepterar två argument, det första är ett objekt som inkluderar en array av strängar som är de obligatoriska fälten för det andra argumentet. t.ex

const configOptions = {
    config1: {
        value: string,
        props: ['firstProp', 'secondProp']
    }
};

myFunc(configOptions.config1, {
  'firstProp': 'first value', // <-- firstProp and secondProp are required because they are defined in config.props
  'secondProp': 'second value',
});

Jag kan uppnå detta genom att referera till den specifika konfigurationen i funktionsdeklarationen och göra en const-assertion på konfigurationsalternativen

const configOptions = {
  config1: {
      value: 'a string value',
      props: ['firstProp', 'secondProp']
  }
} as const;

function myFunc<T extends typeof configOptions.config1>(
  option: T,
  params: { [key in typeof option.props[number]]: string }
) {}

myFunc(configOptions.config1, {
  firstProp: 'first value',
  secondProp: 'second value',
});


Men jag vill uppnå detta mer generellt så att jag kan kalla samma funktion och använda en av många konfigurationer. Är detta möjligt?

Här är en stackblitz https://stackblitz.com/edit/typescript-ydyt4v

Svar #1

Att använda en generisk med en begränsning kommer att tillåta att typen av config härleds. Denna härledda generiska kan sedan användas för att definiera typen för det andra argumentet.

const configOptions = {
    config1: {
        value: 'string',
        props: ['firstProp', 'secondProp']
    },
    config2: {
        value: 'string',
        props: ['one', 'two']
    }
} as const;

// Define the base shape (most general shape) of the config. 
// I used the const assertion (`as const`) above so defining the props as 
// readonly made this work nicely but that could be changed.
interface BaseConfig {
    value: string;
    props: readonly string[]
}

const myFunc = <
    // Define a generic `Config` that adheres (extends) the base shape (BaseConfig).
    // It represents the first argument and is inferred from the passed `config`.
    Config extends BaseConfig
>(
    config: Config,
    // Define the input values type based on the passed `Config`.
    // Specifically, we want the values of the props key.
    // And those keys are for a record, with a string value.
    values: Record<Config["props"][number], string>
) => {
    // TODO: implement as needed.
    return null;
}

// Okay.
myFunc(configOptions.config1, {
    'firstProp': 'first value',
    'secondProp': 'second value',
});

// Property 'secondProp' is missing in type.
myFunc(configOptions.config1, {
    'firstProp': 'first value',
});

// Okay.
myFunc(configOptions.config2, {
    'one': '1',
    'two': '2'
});

// Property 'one' is missing in type.
myFunc(configOptions.config2, {
    'two': '2'
});

TypeScript-lekplatslänk