Reputation: 7360
I want to have an ImmutableJS Map which has to have always defined some properties:
interface MyImmutableMap extends Immutable.Map<string, any> {
a: number;
b: number;
c?: string;
}
I have a function which edits the map:
function myFunction(myMap: MyImmutableMap, flag: boolean, value: number): MyImmutableMap {
if (flag) {
return myMap
.set('a', value);
}
return myMap;
}
The second return is ok, because it returns the same thing I received, therefore an MyImmutableMap.
Unfortunately the set() doesn't work, because its return type is a Map and not the type of myMap.
This is the error:
Type 'Map' is not assignable to type 'MyImmutableMap'.
Property 'a' is missing in type 'Map'.
There is any way to go around this?
Upvotes: 1
Views: 2043
Reputation: 164467
The following interface:
interface MyImmutableMap extends Immutable.Map<string, any> {
a: number;
b: number;
c?: string;
}
Defines an instance of Immutable.Map
which also has the a
, b
and c
properties:
let m: MyImmutableMap;
m.a = 4;
m.b = 6;
m.c = "str";
You cannot control the map key/values like that.
You can do this:
type MyImmutableMap = Immutable.Map<string, any> & {
set(name: "a", value: number);
get(name: "a"): number;
set(name: "b", value: number);
get(name: "b"): number;
set(name: "c", value: string);
get(name: "c"): string | undefined;
}
You can "override" the default set
like so:
type MyImmutableMap = Immutable.Map<string, any> & {
set(name: "a", value: number);
set(name: "a", value: any): void
get(name: "a"): number;
set(name: "b", value: number);
set(name: "b", value: any): void
get(name: "b"): number;
set(name: "c", value: string);
set(name: "c", value: any): void
get(name: "c"): string | undefined;
}
Then:
return myMap
.set('c', 45);
}
Results in:
Type 'void' is not assignable to type 'Map'
It's not the best error message for this case, it's still restricted.
Upvotes: 2
Reputation: 16906
If you only have primitive values, you can also use Record
s instead of a Map
.
Sadly, the typings for immutable.js aren't that good and I am not sure if they are actively maintained by the authors (because the typings are part of the repo and not @types
).
We had a similar issue and used type assertions to overwrite the Map
with a "better" version:
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`
Since the set
of immutable.js is something like set(key:string, value:any)
you would need to use a generic to help the compiler:
declare function Immutable<T>(o: T): Immutable<T>;
interface Immutable<T> {
get<K extends keyof T>(name: K): T[K];
set<S>(key:string, value:any): 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':string }>('lastName', 'Smith');
aliceSmith.get('name'); // Ok, returns a `string`
aliceSmith.get('age'); // Ok, returns a `number`
aliceSmith.get('lastName'); // Ok, returns `string`
Here is a link to the playground.
Upvotes: 3