[Typescript] Dynamic types: Use TypeScript's Mapped Types and Template Literal Types Together
we're going to dive deep into a more complex example in which we combine mapped types, key remapping, template literal types, and indexed access types to statically type a highly dynamic JavaScript function in TypeScript.
Start with following code:
function createGetterObject(obj: any): any { const newObj: any = {}; for (const key of Object.keys(obj)) { const cpK = key[0].toUpperCase() + key.substr(1); const getterKey = `get${cpK}`; newObj[getterKey] = () => obj[key]; } return newObj; } const user = createGetterObject({ name: "Wan", twitter: "zhentiw" }) console.log(user) console.log(user.getName()) console.log(user.getTwitter())
We want to get better typings support.
function createGetterObject<T>(obj: T): PropGetters<T> { const newObj: any = {}; for (const key of Object.keys(obj)) { const cpK = key[0].toUpperCase() + key.substr(1); const getterKey = `get${cpK}`; newObj[getterKey] = () => obj[key]; } return newObj; } type PropGetters<T> = { }
Get an error:
This is because `T` can be any as well. We need to limit T type by telling that `T` can be only for Object:
function createGetterObject<T extends Record<string, any>>(obj: T): PropGetters<T> {
In `PropGetter`, we want to create similar to
type PropGetters<T> = { getName: () => sting getTwitter: () => string }
How to make those?
We can start from:
type PropGetters<T> = { [Key in keyof T]: () => T[Key] }
keyof T: get all the keys of T, so we got `name` & `twitter`
T[Key]: as lookup type, `name` prop result in string `Wan`.
This is what we get as a result:
`name` & `twitter` are functions type which return string, not really what we want.
Template Literal Types
type PropGetters<T> = { [Key in keyof T as `get${Key}`]: () => T[Key] }
Got error:
This is because Key can be string, number, boolean... , what we want is just string type, so we can do:
type PropGetters<T> = { [Key in string & keyof T as `get${Key}`]: () => T[Key] }
Now, we got:
We actual want `getName` not as `getname`, to fix this, we can do:
type PropGetters<T> = { [Key in string & keyof T as `get${Capitalize<Key>}`]: () => T[Key] }
Now the type should works. If we add another prop `id`:
Typescript can tell the return value is a number type.
Final piece to limit `T` in `PropGetters`:
type PropGetters<T extends Record<string, any>> = { [Key in string & keyof T as `get${Capitalize<Key>}`]: () => T[Key] }
相关文章
- [Typescript] Typing a Function Composition with Overloads and Generics
- [Typescript] Using Extract type until to get the value from Union type
- [Typescript] Defining exclusive properties with TypeScript
- [Typescript] Be Specific for Better Inference
- [Typescript] Use Generics in a Reduce Function
- [Typescript] Function scope in typescript
- [Typescript] Tips: Create your own 'objectKeys' function using generics and the 'keyof' operator
- [Typescript] 39. Medium - AnyOf
- [Typescript Challenges] 5. Easy - Length of Tuple
- [Typescript] Making TypeScript Stick - 5 - Extends
- [TypeScript] Interface
- [Typescript] Prevent Type Widening of Object Literals with TypeScript's const Assertions
- [Typescript] Performance Bundling and tslib
- [Typescript] Make TypeScript Class Usage Safer with Strict Property Initialization
- [Typescript Kaop-ts] Use AOP in Vue Components with TypeScript and Kaop-ts
- [TypeScript] Generic Functions, class, Type Inference and Generics
- [Typescript] 40. Medium - IsNever
- [Typescript] Declaration Merging
- [Typescript] Unknow and any Types
- [Typescript] Prevent Type Widening of Object Literals with TypeScript's const Assertions
- [Typescript] Dynamic types: Use TypeScript's Mapped Types and Template Literal Types Together
- [Typescript] Create Type From Any Given Data in TypeScript
- [TypeScript] instanceof and Type Guards (getPrototypeOf)
- [TypeScript] Type Definitions and Modules
- [Typescript 2] Nullable Types - Avoiding null and undefined Bugs