I'm getting into ReactJS and am intrigued but also confused about persistent data structures. I love the idea, but I'm not sure how to take my MV*, Mutable, Observable Bindings experience in designing view components and apply it in a sane way.
For example, suppose I have a deeply nested structure:
Foo
Bar
Baz
someValue
Qux
Quxx
In my UI, I have a component that is an editor for someValue
. In a mutate-observe paradigm I would do something like:
Baz.setSomeValue(newValue) // trigger observers, etc...
As near as I can tell, though, the equivalent with persistent data structures is something like:
Foo = extend(Foo, { Bar:{ Baz:{ someValue: newValue } } });
// recompute with new value of Foo
What is the normal pattern for encapsulating a component that deals with Baz
so that it doesn't have to know the entire structure?
2 Answers 2
What you are searching for is called a "lens" - it is a function which gets or sets a property of a nested object and outputs a new version of the whole object.
There are some good lens libraries in JS, and after you are familiar with the concept it is not hard to implement it yourself.
Part of your trouble is that you're using JavaScript and not a functional programming language like ClojureScript. In ClojureScript you'd have the aid of atoms and update-in
and assoc-in
to help with these nested structural updates. Languages that don't emphasize doing everything with persistent data structures aren't optimized for this use case.
However, that said, there is another way. One thing I've taken to is modeling complex objects (anything made up of key/value pairs) as entities. Each object upon creation would be assigned an immutable ID (I used BSON IDs but GUIDs will do). Then in this manner you're able to eliminate all nesting:
function Entities(state){
this.state = state || {}; //never mutated
}
Entities.prototype.update = function(id, immutableData){...};
Entities.prototype.get = function(id){...};
function Ref(id){
this.id = id; //never mutated
}
var entities = new Entities();
entities = entities.update(1, {id: 1, name: 'Foo'});
entities = entities.update(2, {id: 2, name: 'Bar', children: [new Ref(1)]});
entities = entities.udpate(3, {id: 3, name: 'Baz', children: [new Ref(2)]});
entities = entities.update(3, entities.get(3).set('name', "Bazooka"))
The update
method doesn't mutate, but rather returns a new representation of the modified entity state object;
By using Reference objects, you've effectually eliminated the nesting altogether. You could use the actual integers instead of Ref
s; however, that doesn't make it clear that what you have in your children
array is actually a reference to some other entity. Wrapping it effectively transforms the integer into a Ref
type. It's up to you to interpret the ref type as a pointer to the entity.
Explore related questions
See similar questions with these tags.
setSomeValue
but instead of mutating it in place, efficiently create a new copy.