Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use class model with Redux (with a Mobx option)

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)
like image 425
dagatsoin Avatar asked Mar 01 '16 21:03

dagatsoin


3 Answers

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.

like image 61
markerikson Avatar answered Nov 13 '22 23:11

markerikson


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:

  • shopping-cart
  • tree-view
like image 39
Dan Abramov Avatar answered Nov 14 '22 00:11

Dan Abramov


(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
like image 34
mweststrate Avatar answered Nov 14 '22 00:11

mweststrate