2

Suppose I have an object like this:

const MenuOptions = {
    Project: ["new", "open", "close"],
    File: ["create", "edit"]
    // ...
}

The values of the object are all unique.

I also want to define a method to allow user to click on a menu option:

function clickOnMenuOption(option: string) { ... }

The value of the parameter option can only be one of new/open/close/create/edit/....

Is it possible to define a type MenuOption based on MenuOptions?

type MenuOption = ???
Freewind
  • 193,756
  • 157
  • 432
  • 708

3 Answers3

1

If you just declare MenuOptions as above, the compiler will tend to infer the value types as just string[] instead of a tuple of literals. If you need access to those literals then you will need to change how you declare your object. When TypeScript 3.4 comes out in March 2019 you will be able to use a const context to hint the compiler to infer the narrowest possible type, like this:

const MenuOptions = {
  Project: ["new", "open", "close"],
  File: ["create", "edit"]
  // ...
} as const; // only works in TS3.4+

Until then you can use a helper function like the following to guide the inference:

type Narrowable = string | number | boolean | undefined | null | void | {};   
const tuple = <T extends Narrowable[]>(...t: T)=> t;    

const MenuOptions = {
  Project: tuple("new", "open", "close"),
  File: tuple("create", "edit")
  // ...
};

In either case the type you want is given above as

type MenuOption = (typeof MenuOptions)[keyof typeof MenuOptions][number];

Let's break that apart into steps and give names to the intermediate types. You are taking the type of MenuOptions:

type TypeofMenuOptions = typeof MenuOptions;
// type TypeofMenuOptions = {
//   Project: ["new", "open", "close"];
//   File: ["create", "edit"];
// }

looking up a union of its value types

type MenuOptionsValues = TypeofMenuOptions[keyof TypeofMenuOptions];
// type MenuOptionsValues = ["new", "open", "close"] | ["create", "edit"]

and looking up its numerically-indexed properties (If you have a type (A | B)[K] it is evaluated as A[K] | B[K])

type MenuOption = MenuOptionsValues[number];
// type MenuOption = "new" | "open" | "close" | "create" | "edit"

Which leaves you with the union of string literals you were looking for.

Okay, hope that helps. Good luck!

jcalz
  • 264,269
  • 27
  • 359
  • 360
0

You can use TypeScript Enums. They are best suited for declaring constants.

enum MenuOption {
    New = "new",
    Open = "open",
    Close = "close",
    Create = "create",
    Edit = "edit"
}
const MenuOptions = {
    Project: [MenuOption.New, MenuOption.Open, MenuOption.Close],
    File: [MenuOption.Create, MenuOption.Edit]
}
function clickOnMenuOption(option: MenuOption) { //... }
Fullstack Guy
  • 16,368
  • 3
  • 29
  • 44
0

I don't know what your experience with TS is, but you seem to make typical TS-newbie errors (confusing types with values), sorry if I am wrong.

Idiomatic TS code should look like this:

type Option = "new" | "open" | "close" | "create" | "edit"

interface MenuOptions {
    Project: Option[],
    File: Option[]
}

const menuOptions: MenuOptions = {
    Project: ["new", "open", "close"],
    File: ["create", "edit"]
    // ...
}

function clickOnMenuOption(option: Option) {  }
Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89
  • Thanks! I made another simpler question with clearer example code, please see: https://stackoverflow.com/questions/54607961/how-to-define-a-type-based-on-values-of-an-array – Freewind Feb 09 '19 at 16:00