简体   繁体   中英

Using the keys of an object literal as a Typescript type?

I have an object which contains some predefined data for my application, which is stored in a const variable like this:

const data:{[key:string]:any} =Object.freeze({
    some: 123,
    long: {"a":"b"},
    list: ["c"],
    of: "",
    arbitrary: null,
    things: 1.2,
});

The keys of this object are known to the rest of the application. Consider this function which accesses the data object:

function doWork(k) {
    if(!data.hasOwnProperty(k)) throw Error();
    let value = data[k];
    //...
}

This is called with strings like

doWork("things");

I would like to replace that runtime error for invalid keys with a Typescript compile-time check. I would like to be able to write

function doWork(k: keyof data) {
    let value = data[k];
    //...
}

But apparently the keyof operator does not work that way. I get an error TS2304: Cannot find name 'data'.


My workaround: I can extract the keys of the object with something like this:

console.log("\""+Object.keys(data).join("\"|\"")+"\"");

Which I can then copy/paste and define as a type.

type data_key = "some"|"long"|"list"|"of"|"arbitrary"|"things"
export function doWork(k:data_key) {
    let value = data[k];
    //...
}

This feels like a silly hack and is quite inconvenient whenever I have to make a change, because I have to remember to put in this statement at the right place, run the program, and manually copy in the values back into the source code (or realistically, just type in the changes myself).


I am open to a better solution. Is there a language feature that provides the functionality I am looking for?

Let TypeScript infer the type of data , then extract the keys from the type it infers by using type data_key = keyof typeof data; :

const data = Object.freeze({
    some: 123,
    long: {"a":"b"},
    list: ["c"],
    of: "",
    arbitrary: null,
    things: 1.2,
});

type data_key = keyof typeof data;

function doWork(k: data_key) {
    let value = data[k];
    //...
}

On the playground.

How that works:

  1. TypeScript has advanced type inference and so is able to infer the type of the object initializer passed into Object.freeze with the keys some , long , list , etc. Object.freeze is defined as freeze<T>(o: T): Readonly<T> ¹ so it returns a Readonly version of that same inferred type.
  2. keyof gets the keys of a type.
  3. typeof in this context is TypeScript's typeof , not JavaScript's. In TypeScript, there are places it expects runtime value, and other places it expects compile-time types. keyof 's operand is a place where a compile-time type is expected, so typeof data returns the compile-time type of data , which is the Readonly version of the inferred type from freeze (with the keys some , long , list , etc.). (If you had x = typeof data , that would be typeof in a context where a runtime value is expected, so it would be JavaScript's typeof , and x would get the runtime value "object" .)

¹ There are actually three definitions of freeze in lib.es5.d.ts (one for arrays, one for functions, and one for all other kinds of objects; this is that last one).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM