-
-
Notifications
You must be signed in to change notification settings - Fork 852
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for Set and Map #146
Comments
I had a quick try at adding Set and Map for es6 environment. I don't completely get everything happening in immer, so I well may have missed some complexities. At first glance it seems actually pretty easy to proxy these since all mutations happen through method calls: const nonMutatingMethods = [
"forEach",
"get",
"has",
"keys",
"set",
"values",
"entries",
"@@iterator"
]
const mutatingMethods = ["set", "delete", "clear", "add"]
const properties = ["size"]
function source(state) {
return state.modified ? state.copy : state.base
}
export function proxyMapOrSet(state) {
var proxyState = state
var proxy = {}
nonMutatingMethods.forEach(function(method) {
proxy[method] = function(...args) {
return source(proxyState)[method](...args)
}
})
properties.forEach(function(prop) {
proxy[prop] = source(proxyState)[prop]
})
mutatingMethods.forEach(function(method) {
proxy[method] = function(...args) {
markChanged(proxyState)
return proxyState.copy[method](...args)
}
})
createHiddenProperty(proxy, PROXY_STATE, proxyState)
return {proxy}
} I guess you don't need to recurse into the Map/Set values since they are all set through method calls? |
but it seems you can't freeze these, which means you have to do something kind of hacky there I guess:
|
Tried it out today here https://github.com/RikuVan/immer/tree/support_map_and_set with only a few basic tests |
So I wanted to get full support for ES6 Map/Set. I also needed to get change logs of every update -- since the proxy makes this pretty efficient I took my own stab at re-building the library a bit. immutaIt is still a little messy since I was just getting it into the fold but the example does a good example of showing it in action: import immuta from "immuta";
import printDifference from "immuta/utils/print-difference";
const state = {
deep: {
foo: {
bar: {
baz: true
}
},
set: new Set([{ one: "two" }, { two: "three" }]),
map: new Map([["one", { foo: "bar" }]]),
array: [{ i: 1 }, { i: 2 }, { i: 3 }, { i: 4 }, { i: 5 }]
}
};
const next = immuta(
// provide the state to start with
state,
// draft is a proxy that will copy-on-write
draft => {
const one = draft.deep.map.get("one");
if (one) {
one.foo = 1;
}
draft.deep.set.clear();
draft.deep.set.add({ some: "obj" });
draft.deep.array[2].foo = "bar!";
},
// optional callback for change events
(changedState, changedMap, rollback) => {
// rollback() will cancel the changes and return original object
// changedMap is Map { 'path.to.change' => changedValue }
// changedState is the new state being returned to caller (nextState)
changedMap.forEach((v, changedKey) => {
console.log("Change: ", changedKey);
});
}
);
printDifference(state, next);
As for the Object.freeze - you can't freeze it directly using Object.freeze() but you could certainly use a proxy to do it. Not quite there yet. Probably the most annoying part is having to handle stepping multiple times when receiving functions (to determine if the function is supposed to belong to our Map/Set). Question also becomes whether or not the support should instead be "Set-like and Map-like instances" rather than requiring it be a true For me I also needed to add the extra syntax and RE which will be a perf hit - plan to rethink how that is implemented shortly. It should go as deep as needed. It will obviously not, and never should, support Although I've done some thinking about what it would look like to take this to the next level and support function calls with given arguments (since Set/Map required the creation of such a concept). This would be kind of insane/interesting but probably a bad idea/for another lib :-P Hoping my efforts here might be of some use in some way to y'all! Thanks for the concept, it's solid! Update - Just to update - got one last blocker in place but believe im able to rid most of the hurdles here using this format and it should translate well into |
Cool, I am curious to look at your version more carefully as soon as I have more time. |
Yeah it is working well now and performs very well. As for mentioning not needing to recurse, I disagree. Take this example: const state = {
deep: {
foo: {
map: new Map([['one', { foo: 'bar' }]]),
},
};
const next = immuta(
// provide the state to start with
state,
// draft is a proxy that will copy-on-write
draft => {
const val = draft.deep.map.get('one');
val.baz = 'qux';
},
// optional callback for change events
(changedState, changedMap, rollback) => {
// rollback() will cancel the changes and return original object
// changedMap is Map { 'path.to.change' => changedValue }
// changedState is the new state being returned to caller (nextState)
changedMap.forEach((v, changedKey) => {
console.log('Change: ', changedKey);
});
},
); Which I need to improve still but it works for all cases I've tested against thus far. Just would be issue with non-confirming prototype methods so need to do an instance check |
I provided some more insight into some of the challenges with Essentially Merge is essentially impossible because of this, when insertion order is no longer maintained there is really no way to "map" the desired values to their originals that we want to merge with. Overall Map is pretty perfect (aside from keys not being immutable), but Set is fair to say is almost guaranteed to cause problems when merging - and possible to cause issues when doing standard mutations in a draft (using iteration) -- which would be fairly slow anyway since it cant use mergeWithDraft and must iterate the proxy. |
For now added some docs on how to work with Map's and Set's in combination with Immer: Closing this issue for now, but if PR shows up that supports both Maps and Sets completely, with deep updates and also works in ES5 mode :) |
Someone might want to add this PR? :) Support for maps and set is the last thing that is missing in this cool library. Then I could get rid of the dependence on immutable.js |
@mweststrate what if a PR for ES6 only is going to show up? Moreover, what if it is only for Maps?
Would it be beneficial for the community to have at least ES6 Maps support? Would you be willing to merge such a PR? |
@mweststrate I also have a question on the design. Would you expect a mutation of a Map item to cause a creation of a new Map? In other words, should this work? const state = {
users: new Map([["michel", {name: "miche"}]])
}
const nextState = produce(state, draft => {
const user = draft.users.get("michel")
// Should this one work, effectively creating a new Map?
user.name = 'Aladdin'
}) |
Yes :)
…On Sun, Apr 14, 2019 at 9:38 PM Andrey Goncharov ***@***.***> wrote:
@mweststrate <https://github.com/mweststrate> I also have a question on
the design. Would you expect a mutation of a Map item to cause a creation
of a new Map?
In other words, should this work?
const state = {
users: new Map([["michel", {name: "miche"}]])
}
const nextState = produce(state, draft => {
const user = draft.users.get("michel")
// Should this one work?
user.name = 'Aladdin'
})
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#146 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABvGhL9GjxMi-EVHIFrSHTm7ICQVeJImks5vg4OkgaJpZM4Tp_Wl>
.
|
So far I managed to make an early preview of nested updates for ES6 Maps. The ongoing work can be tracked here. If anyone is interested in providing any kind of help with this feature please email me at [email protected] |
@mweststrate PR for ES6 maps #350 |
Reopened this issue, as the PR is still pending, |
@mweststrate @aleclarson Guys look at this #437 |
any updates on this? |
If the PR is solid, it's coming, but didn't have any time to review yet. |
It would really help us because we have built our state with Set and
appears that it became mutable and mutates primary object...
…On Fri, Oct 11, 2019, 1:35 AM Michel Weststrate ***@***.***> wrote:
If the PR is solid, it's coming, but didn't have any time to review yet.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#146?email_source=notifications&email_token=AA54ZF6DLJ7T6JRYETSSHY3QN632RA5CNFSM4E5H6WS2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEA6JEPI#issuecomment-540840509>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AA54ZF752ENTHQAE2SUNCCTQN632RANCNFSM4E5H6WSQ>
.
|
This has been released as Immer 5.0! |
Set
,Map
,WeakMap
, etc?The text was updated successfully, but these errors were encountered: