Last active
August 7, 2023 18:13
-
-
Save JakeSidSmith/a5ddd931c2b00472ab232b95077b91cb to your computer and use it in GitHub Desktop.
Strict types in TypeScript with getters and setters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Tested with TypeScript 2.4.2 */ | |
// First we define an interface for our object | |
interface IThing { | |
id: string; | |
type: 'foo' | 'bar'; | |
num?: number; | |
} | |
// Then we can use this Exact type to prevent type inference on our objects | |
type Exact<T> = {[K in keyof T]: T[K]}; | |
// We call Exact with our objects interface | |
// This ensures that our object is always created with the exact keys & values required by the interface | |
let thing: Exact<IThing> = { | |
id: 'test', | |
type: 'foo', | |
num: 1, | |
}; | |
// Now when we access the keys, all of the types of these values are the exact types defined in our interface | |
let id = thing.id // string | |
let type = thing.type // 'foo' | 'bar' | |
let num = thing.num // number | undefined | |
// Here we're defining an API with getters and setters for our object | |
// The objects type (I) is inferred, and if we're passing an Exact object, it'll know all the interface types | |
function MagicMap<I> (obj: I) { | |
// In order to return the correct types we extend the keys of this object | |
// Don't ask me why, but if you don't let K extend, you get less exact types | |
function get<K extends keyof I> (k: K) { | |
return obj[k]; | |
} | |
// Same for the setter | |
// But here we are also passing a value that uses K to get the exact values that can be passed in to the setter also | |
function set<K extends keyof I> (k: K, value: typeof obj[K]) { | |
obj[k] = value; | |
return obj; | |
} | |
return { | |
get, | |
set, | |
}; | |
} | |
const magicMap = MagicMap(thing); | |
// Now all of the types returned by the getters are the exact types you'd expect from the interface | |
id = magicMap.get('id'); // string | |
type = magicMap.get('type'); // 'foo' | 'bar' | |
num = magicMap.get('num'); // number | undefined | |
// And it wont let you set a less exact value on any key | |
// E.g. thing's type is 'foo' | 'bar', so simply passing another string is not allowed | |
thing = magicMap.set('type', 'string'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment