I have a react-redux application written in typescript with immutable package. There I have a data, which comes from api and in store I pack it to Map. In all application th
Leaving that here in case it helps someone:
import { Map } from 'immutable';
export interface ImmutableMap<JsObject extends object> extends Omit<Map<keyof JsObject, JsObject>, 'set' | 'get' | 'delete'> {
set: <AKeyOfThatJsObject extends keyof JsObject>(key: AKeyOfThatJsObject, value: ValueOf<Pick<JsObject, AKeyOfThatJsObject>>) => ImmuMap<JsObject>;
get: <AKeyOfThatJsObject extends keyof JsObject>(key: AKeyOfThatJsObject) => ValueOf<Pick<JsObject, AKeyOfThatJsObject>>;
delete: <AKeyOfThatJsObject extends keyof JsObject>(key: AKeyOfThatJsObject, value: ValueOf<Pick<JsObject, AKeyOfThatJsObject>>) => ImmuMap<JsObject>;
}
It's really working great for us!
We use it like so: const aMap: ImmutableMap<OneOfOurObjectTypes>
Example Usage:
type BarType = { bar: string };
const foo: BarType = {
bar: 'abc';
};
const immutableBar: Immutable<BarType> = Map(foo);
// immutableBar.get suggests bar as the key, and knows that the result is a string.
We used it like this in our project (slightly different approach):
interface ImmutableMap<T> extends Map<string, any> {
get<K extends keyof T>(name: K): T[K];
}
We used an older version of the Immutable.js
typings that didn't used mapped types yet (T[K]
). AFAIK typings are updated since then and there is no need to overwrite the get
method.
EDIT: Actually the get
method still is not fully type safe unlike the above. So overwriting the method still has its merits.
With the above declaration you can then create immutable maps like:
type AuthState = ImmutableMap<{
user:string|null;
loggedIn:boolean;
}>;
const authState:AuthState = fromJS({ user: 'Alice', loggedIn: true });
Ideally, you would like typings like this:
/**
* Imaging these are typings for your favorite immutable
* library. We used it to enhance typings of `immutable.js`
* with the latest TypeScript features.
*/
declare function Immutable<T>(o: T): Immutable<T>;
interface Immutable<T> {
get<K extends keyof T>(name: K): T[K];
set<S>(o: S): Immutable<T & S>;
}
const alice = Immutable({ name: 'Alice', age: 29 });
alice.get('name'); // Ok, returns a `string`
alice.get('age'); // Ok, returns a `number`
alice.get('lastName'); // Error: Argument of type '"lastName"' is not assignable to parameter of type '"name" | "age"'.
const aliceSmith = alice.set({ lastName: 'Smith' });
aliceSmith.get('name'); // Ok, returns a `string`
aliceSmith.get('age'); // Ok, returns a `number`
aliceSmith.get('lastName'); // Ok, returns `string`
Link to the Playground
In order to achieve the above with Immutable.js
you can create a small helper function, whose only purpose is to "fix" typings:
import { fromJS } from 'immutable';
interface Immutable<T> {
get<K extends keyof T>(name: K): T[K];
set<S>(o: S): Immutable<T & S>;
}
function createImmutable<T extends object> (o:T) {
return fromJS(o) as Immutable<T>;
}
Note that I used fromJS
in the example. This will create a Map
as long as the passed input is an Object
. The benefit of using fromJS
over Map
is that the typings are easier to overwrite.
Side note: You might also want to look into Record
s.