Home > Software engineering >  How to do this in Typescript: using the default value when the computed property does not exist?
How to do this in Typescript: using the default value when the computed property does not exist?

Time:12-01

When using Computed Property in javascript, I can write my code like this

const def_val = {a:"debug",b:"info",c:"warning"};

function work(y) {
    let x = def_val[y] || def_val.a /* if y is not a or b or c */
}

But how to I make that work in typescript ?

const def_val = {a:"debug",b:"info",c:"warning"};

function work(y: string) {
    let x = def_val[y] || def_val.a;
}

I got the compiler error

error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ a: string; b: string; c: string; }'.

No index signature with a parameter of type 'string' was found on type '{ a: string; b: string; c: string; }'.

I have searched SO but I can only find How to set a variable if undefined in typescript? which is not my question.

Now I changed my ts code to these, but it feels tedious compared to the original js code

function work(y: string) {
    let x:string
    if (y!='a' && y!='b' && y!='c') {
        x = def_val.a;
    } else {
        x = def_val[y]
    }
}

---- update ----

@captain-yossarian answer is one way of fix it, e.g. const def_val: Record<string, string> = { a: "debug", b: "info", c: "warning" };

I find the other way to fix it is to use keyof typeof, check here https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types for further information, e.g. "In JavaScript it is fairly common to have APIs that expect property names as parameters, but so far it hasn’t been possible to express the type relationships that occur in those APIs.", exactly my case here!!

const def_val = {a:"debug",b:"info",c:"warning"};

function work(y:keyof typeof def_val) {
    let x = def_val[y] || def_val.a;

}

I got this answer from How to dynamically access object property in TypeScript

CodePudding user response:

def_val is infered by typescript as

const def_val: {
    a: string;
    b: string;
    c: string;
}

y argument has string type

def_val expects 'a' | 'b' | 'c' as a keys. It means that typescript allows you to use only these keys with def_val. Since string type is much wider than 'a' | 'b' | 'c' you are getting error.

y:string means that it allows you to pass foo property and def_val['foo'] is not safe since foo does not exists in def_val.

In order to fix it, you should provide explicit type for def_val:

const def_val: Record<string, string> = { a: "debug", b: "info", c: "warning" };

function work(y: string) {
    let x = def_val[y] || def_val.a // ok
}

IF you are not allowed to use explicit type on def_val, you can provide def_val as an argument to work:


const def_val = { a: "debug", b: "info", c: "warning" };

function work<Def extends Record<string, string>>(def: Def, y: string) {
    let x = def[y] || def.a
}
work(def_val, 'foo') // ok

You also can use custom typeguard:


const def_val = { a: "debug", b: "info", c: "warning" };

const isValidKey = (key: string): key is keyof typeof def_val =>
    /a|b|c/.test(key)

const work = (y: string) => isValidKey(y) ? def_val[y] : def_val.a

work('foo') // ok

P.S. Here you can find documentation about using built in utility types , like Record, Partial, etc ...

  • Related