Home > Net >  mixing interface types TS
mixing interface types TS

Time:12-10

Having a TS issue which I thought would be simpler to implement than it is. I have an interface which describes an object of key/values, the majority strings with one exception. Its a long list something like

export const list = {
   itemOne: 'bla',
   itemTwo: 'bla bla',
   itemThree: 'bla bla bla',
   itemFour: false

}

I want an interface for this without explicitly typing out each item, something like

import { list as keys } from 'the_above'

interface listTypes { 
    [key: string]: string;
    [`${keys.itemFour}`]: boolean;
}

I get this error message, which I don't fully understand 'A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.'

Any help would be appreciated as I'm still shaky with TS

CodePudding user response:

You can use a const assertion to prevent the compiler from widening the types of the values in your list object: the compiler will infer the literal values instead. This is important in regard to how the compiler infers the type of the template literal string for the key with the boolean type value.

Also: TypeScript offers another way to define types that represent objects (data structures with indexed values): a type alias. The TS handbook section Differences Between Type Aliases and Interfaces begins with this information:

Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.

If there's not a reason that you need to keep the type open to be mutated by other code, then a type alias might be a better choice in this case.

Here's an example:

Note: in the playground and code shown below, I've enabled the compiler option noUncheckedIndexedAccess for stronger type safety. See also the utility type Record<Keys, Type>.

TS Playground

module1.ts:

export const list = {
  itemOne: 'bla',
  itemTwo: 'bla bla',
  itemThree: 'bla bla bla',
  itemFour: false,
} as const;
//^^^^^^^^
// const assertion

module2.ts:

import { list as keys } from './module1';

type ListTypes = Record<string, string> & Record<`${typeof keys['itemFour']}`, boolean>;

Here are some example statements showing compiler inference: they illustrate that the type represents what you asked about in your question:

type ListTypeKeyWithBooleanValue = keyof {
  [
    K in keyof ListTypes as ListTypes[K] extends boolean
      ? K
      : never
  ]: unknown;
};

declare const k: ListTypeKeyWithBooleanValue;
            //^? const k: "false"

declare const l: ListTypes;
l.false;
//^? (property) false: boolean

l.true;
//^? string | undefined

l.anotherProperty;
//^? string | undefined

  • Related