9

EDIT: I finally choosed Mobx.js, refer to @mweststrate answer for details.

All learning ressources about redux show how to use it with plain object models. But I can't figure out how to use it when you use some es6 Class models.

For example, let's take this state shape:

{
 players:{
   000:{
     life:56,
     lvl:4,
     //...
  },
   023:{
     life:5,
     lvl:49,
     //...
  },
   033:{
     life:679,
     lvl:38,
     //...
  },
   067:{
     life:560,
     lvl:22,
     //...
  },
  //...
}

And this class (not tested)

class Player{
  id; //int
  life; //int
  lvl; //int
  buffs; //[objects]
  debuffs; //[objects]
  inventory; //[objects]

  _worldCollection; //this class know about the world they belongs to.

  constructor({WorldCollection}){
    this._worldCollection = WorldCollection;
  }

  healPlayer(targetId, hp){
   this._worldCollection.getPlayer(targetId).setHealth(hp);
  }

  // setter
  setHealth(hp){
    this.life += hp;
  }
}

Imagine I have a collection of 100 players in WorldCollection. What is the best way?

Take 1: copying all properties from instance to the state tree

{
  players:{
    001:{
      life: 45,
      lvl: 4,
      buffs: [objects]
      debuffs:[objects]
      inventory:[objects]
    },
    034:{
      life: 324,
      lvl: 22,
      buffs: [objects]
      debuffs:[objects]
      inventory:[objects]
    },
    065:{
      life: 455,
      lvl: 45,
      buffs: [objects]
      debuffs:[objects]
      inventory:[objects]
    },
  //...
}

This could be done by injecting dispatch in the constructor

//...
constructor({WorldCollection, dispatch})
//...

Dispatch an action in each setter.

// setter
setHealth(hp){
  this.life += hp;
  dispatch({type:"HEAL_PLAYER", data:{id:this.id})
}

And put all the logic in reducers (setter logic being deterministic and atomic).

...
case "HEAL_PLAYER":
  return {
    ...state,
    life: state.life + action.hp
  };
...

Pro:

  • IMHO It seems to me more redux way to have only one place where all the state is.

Cons:

  • All logic is decentralized from the model in another place. I don't like to multiply files. But maybe it is not a real problem?
  • Redux says the logic has to be put in actions, not in reducers.
  • The state takes twice more memory. I saw that immutables.js could optimize this but I am not sure.

Take 2: Storing only ids in the redux state tree

{
  players:[
    001,
    002
    //...
  ]
}

This could be done by also using dispatch in each setter and dispatch an action after each setter

// setter
setHealth(hp){
  this.life += hp;
  dispatch({type:"PLAYER_UPDATED", data:{id:this.id})
}

When the new tree state is changed. I call mapStateToProps and WorldCollection.getPlayer() to retrieve the right instance and map its properties to the view.

Pros:

  • Redux way is respected by not putting logic in reducers
  • Not "duplicated state" (if Immutables.js can't optimise this)
  • Logic is in the model (makes more sense for me)

Cons:

  • Redux state does not represent the whole state

I hope I have not simplified the case too much. My point is to clarify if/how redux could be use with some class models.

Take 3: Use Mobx.js instead/with Redux

--- very pre-experimental here ---

I discovered Mobx.js a week ago and its simplicity/perf had me.

I think we could observe each class members (which together form the app state)

  @observable life; //int
  @observable lvl; //int
  @observable buffs; //[objects]
  @observable debuffs; //[objects]
  @observable inventory; //[objects]

and somewhere else have a class which builds the state tree, maybe Redux could make sense here? (Note I have no clue how to do this part. Have to dig more deeply in Mobx)

This is pros/cons in a pure redux/mobx comparaison for my case.

Pros:

  • Less verbose
  • No Model to inherited from (like in redux-orm)
  • Performance has been evaluated (So I barely know where I would be going to)
  • Don't write "opiniated" reducers in the class model (just mutators)

Cons:

  • No idea how to implement a redo/undo (or a jitter buffer in game dev)
  • It does not seem to be a "tree" like in redux to have the whole state in a blink (for me it is the killer feature of redux)
Community
  • 1
  • 1
dagatsoin
  • 2,626
  • 6
  • 25
  • 54

3 Answers3

10

I would like to add that if you were to go with Redux you would not store state in classes at all. In Redux, this logic would be described in reducers which would operate on plain objects rather than class instances. You would keep the data normalized so each entity is kept in an object map by its ID, and any reference to child entities would be an array of IDs rather than a real reference. You would then write selectors to reconstruct the parts of the data you care about for rendering.

You might find this discussion helpful, as well as these two examples:

Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
6

(MobX author). For a short answer on the questions about MobX:

Redo / undo can be implemented in two ways:

  1. Use the action / dispatcher model from flux: dispatch serializable actions, and interpret them to update the state model. This way you can build an append only action log and base undo / redo on that.
  2. Automatically serialize your state model into a state history (which uses structural sharing). The reactive-2015 demo demonstrates this nicely: https://github.com/mobxjs/mobx-reactive2015-demo/blob/master/src/stores/time.js. During this serialization you could also generate delta patches instead of a complete state tree if that is easier to process.

Single state tree:

  1. In MobX there should also be a single source of truth. The main difference with Redux is that it doesn't prescribe you were to store it. Nor does it enforce you to have a tree. A graph would do fine as well. Getting a snapshot of that graph can simple be done by leveraging mobx.toJson or by using solution previous point 2. of redo / undo.
  2. To make sure everything is in one connected graph (which you like), just create a root state objects that points to the player and world collection (for example). But unlike Redux, you don't have to normalize from there on. World can just have direct references to players and vice versa. A single state root object is created in the reactive-2015 demo as well: https://github.com/mobxjs/mobx-reactive2015-demo/blob/master/src/stores/domain-state.js
mweststrate
  • 4,890
  • 1
  • 16
  • 26
  • Very cool! The graph possibility could perfectly fit with my needs (tree was a kind of fallback in my architecture). I have now to assimilate all this infos first before take any conclusion. Thanks you very much! – dagatsoin Mar 02 '16 at 10:54
  • What do you mean by leveraging mobx.toJson? Is it a package or I missed something. – dagatsoin Mar 02 '16 at 13:44
  • Its like JSON.stringify, but supports cycles. Creates non-observable clones of your object graph: http://mobxjs.github.io/mobx/refguide/tojson.html – mweststrate Mar 02 '16 at 14:19
  • Ok thx. Do you have an idea of the memory impact to maintain a state graph aside all models instance? In short, are they just references memory adresse pointers, very light, or sync copies doubling the memory size? – dagatsoin Mar 02 '16 at 15:45
  • You mean for redo/undo solution two? There you get a copy for each object that is changed, unchanged objects are shared trough the history. So that should be comparable to having a state history in Redux. (but MobX will in generally consume a bit more memory then Redux for the state tracking, on the other hand it should use less CPU on keeping complex models in sync with your UI). – mweststrate Mar 02 '16 at 16:02
  • Yes for the redo/undo solution two. For sharing the common objects, I think Redux has to use Immutable.js. About the CPU, is there a linear curve between the complexity of an object (eg. my WorldState with thousands of objects) and the CPU usage? And if yes, have you any ressource on good practices to optimize such an object? – dagatsoin Mar 02 '16 at 16:15
  • No not really, CPU usage is mainly determined by the number of _active_ observers. So for example the number of components being rendered on the screen. That is more relevant than the data size. (And we are currently rendering a few thousands components simultaneously). In our tests observers used less CPU than selectors for our model. But its hard to make make general statements about it. But feel free to extensively test it :) – mweststrate Mar 02 '16 at 19:12
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/105187/discussion-between-danieln-and-mweststrate). – dagatsoin Mar 02 '16 at 19:49
  • Very interesting. This could definitely fit with my MMO project. Thank you very much for your time! – dagatsoin Mar 02 '16 at 20:01
  • 1
    Do you mind if I check @makerikson answer? Indeed redux-orm perfectly fits the need but after comparing both architectures I have gone with Mobx. So you lose the answer but win a user. Sounds fair :) ? – dagatsoin Mar 02 '16 at 20:15
  • sounds fine. Enjoy MobX :) – mweststrate Mar 03 '16 at 17:42
4

You might want to look at redux-orm, which pretty much does this already. It provides a nice Model-like facade over the actual plain object data in your Redux state, and handles relational data very nicely.

markerikson
  • 63,178
  • 10
  • 141
  • 157
  • Soons very good. However I wonder what are the performances (since the author "warns" about it) with a lot of entities. (My client could handle 100+ at the same time, but the server - whit a lot of RAM - have to manage millions of them). I am currently looking at Mobx.js package to compare. But I don't know if I could use Redux with Mobx.js. Seem too different. – dagatsoin Mar 02 '16 at 09:01
  • I will edit to have a (non proved) third take with mobx.js. – dagatsoin Mar 02 '16 at 09:06