9

编辑:我最终选择了 Mobx.js,详情请参阅@mweststrate 答案。

所有关于 redux 的学习资源都展示了如何将它与普通对象模型一起使用。但是当你使用一些 es6 Class 模型时,我无法弄清楚如何使用它。

例如,让我们采用这种状态形状:

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

而这个类(未测试)

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;
  }
}

想象一下,我在 WorldCollection 中有 100 个玩家的集合。什么是最好的方法?

采取1:将所有属性从实例复制到状态树

{
  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]
    },
  //...
}

这可以通过注入dispatch构造函数来完成

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

在每个 setter 中调度一个动作。

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

并将所有逻辑放在 reducer 中(setter 逻辑是确定性和原子性的)。

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

临:

  • 恕我直言,在我看来,只有一个地方是所有状态的地方。

缺点:

  • 所有逻辑都从模型中分散到另一个地方。我不喜欢增加文件。但也许这不是一个真正的问题?
  • Redux 说逻辑必须放在 action 中,而不是 reducer 中。
  • 该状态需要两倍的内存。我看到immutables.js可以对此进行优化,但我不确定。

采取 2:在 redux 状态树中只存储 id

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

这可以通过使用dispatchin eachsetter并在每个 setter 之后调度一个动作来完成

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

当新的树状态改变时。我调用mapStateToPropsWorldCollection.getPlayer()检索正确的实例并将其属性映射到视图。

优点:

  • 尊重 Redux 方式,不将逻辑放入 reducer
  • 不是“重复状态”(如果 Immutables.js 无法优化)
  • 逻辑在模型中(对我来说更有意义)

缺点:

  • Redux 状态不代表整个状态

我希望我没有把这个案子简化太多。我的意思是澄清 redux 是否/如何与某些类模型一起使用。

采取 3:使用 Mobx.js 代替/与 Redux

---这里非常预实验---

一周前我发现了 Mobx.js,它的简单性/性能吸引了我。

我认为我们可以观察每个班级成员(共同构成应用程序状态)

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

在其他地方有一个构建状态树的类,也许 Redux 在这里有意义?(注意我不知道如何做这部分。必须在 Mobx 中更深入地挖掘)

对于我的情况,这是纯粹的 redux/mobx 比较中的优点/缺点。

优点:

  • 不那么冗长
  • 没有要继承的模型(如在 redux-orm 中)
  • 性能已经过评估(所以我几乎不知道我要去哪里)
  • 不要在类模型中编写“自以为是”的 reducer(只是 mutators)

缺点:

  • 不知道如何实现重做/撤消(或游戏开发中的抖动缓冲区)
  • 眨眼间拥有整个状态似乎不像 redux 中的“树”(对我来说这是 redux 的杀手级功能)
4

3 回答 3

10

我想补充一点,如果你使用 Redux ,你根本不会在类中存储状态。在 Redux 中,这个逻辑将在 reducer 中描述,它会在普通对象而不是类实例上操作。您将保持数据标准化,以便每个实体通过其 ID 保存在对象映射中,并且对子实体的任何引用都将是 ID 数组而不是真正的引用。然后,您将编写选择器来重建您关心的数据部分以进行渲染。

您可能会发现此讨论以及以下两个示例很有帮助:

于 2016-03-05T11:54:39.050 回答
6

(MobX 作者)。关于 MobX 问题的简短回答:

重做/撤消可以通过两种方式实现:

  1. 使用 Flux 中的动作/调度器模型:调度可序列化的动作,并解释它们以更新状态模型。通过这种方式,您可以构建一个仅附加的操作日志并以此为基础撤消/重做。
  2. 自动将您的状态模型序列化为状态历史(使用结构共享)。reactive-2015 演示很好地展示了这一点:https ://github.com/mobxjs/mobx-reactive2015-demo/blob/master/src/stores/time.js 。在这个序列化过程中,如果更容易处理,您还可以生成增量补丁而不是完整的状态树。

单状态树:

  1. 在 MobX 中也应该有一个单一的事实来源。Redux 的主要区别在于它没有规定你要存储它。它也不会强制你拥有一棵树。图表也可以。可以通过利用 mobx.toJson 或使用解决方案之前的第 2 点重做/撤消来简单地获取该图的快照。
  2. 为确保一切都在一个连接图中(您喜欢),只需创建一个指向玩家和世界集合的根状态对象(例如)。但与 Redux 不同的是,您不必从那里进行规范化。World 可以直接引用玩家,反之亦然。在 reactive-2015 演示中也创建了单个状态根对象:https ://github.com/mobxjs/mobx-reactive2015-demo/blob/master/src/stores/domain-state.js
于 2016-03-02T10:46:04.933 回答
4

你可能想看看redux-orm,它几乎已经做到了。它在 Redux 状态下的实际普通对象数据上提供了一个很好的类似模型的外观,并且非常好地处理关系数据。

于 2016-03-01T22:29:14.280 回答