编辑:我最终选择了Mobx.js,有关详细信息,请参阅@mweststrate答案。
所有关于 redux 的学习资源都展示了如何将其与普通对象模型一起使用。但是当您使用某些 es6 类模型时,我不知道如何使用它。
例如,让我们采用以下状态形状:
{
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
setHealth(hp){
this.life += hp;
dispatch({type:"HEAL_PLAYER", data:{id:this.id})
}
并将所有逻辑放在化简器中(二传器逻辑是确定性的和原子的)。
...
case "HEAL_PLAYER":
return {
...state,
life: state.life + action.hp
};
...
优点:
- 恕我直言,在我看来,只有一个地方所有状态都是更重复的方式。
缺点:
- 所有逻辑都从另一个地方的模型中分散。我不喜欢增加文件。但也许这不是一个真正的问题?
- Redux说,逻辑必须付诸行动,而不是化简器。
- 状态占用的内存多出两倍。我看到不可变的.js可以优化这一点,但我不确定。
采取 2:在 redux 状态树中仅存储 id
{
players:[
001,
002
//...
]
}
这可以通过在每setter
中使用dispatch
并在每个二传手之后调度一个动作来完成
// setter
setHealth(hp){
this.life += hp;
dispatch({type:"PLAYER_UPDATED", data:{id:this.id})
}
更改新树状态时。我调用 mapStateToProps
和 WorldCollection.getPlayer()
来检索正确的实例并将其属性映射到视图。
优点:
- Redux 方式通过不将逻辑放入化简器来尊重
- 不是"重复状态"(如果不可变.js则无法优化)
- 逻辑在模型中(对我来说更有意义)
缺点:
- Redux 状态不代表整个状态
我希望我没有把案件简化太多。我的观点是澄清 redux 是否/如何与某些类模型一起使用。
采取3:使用Mobx.js改用/使用Redux
---在这里非常预先实验---
一周前.js我发现了Mobx,它的简单/性能让我受益匪浅。
我认为我们可以观察每个类成员(它们共同构成应用程序状态)
@observable life; //int
@observable lvl; //int
@observable buffs; //[objects]
@observable debuffs; //[objects]
@observable inventory; //[objects]
其他地方有一个构建状态树的类,也许 Redux 在这里有意义?(注意我不知道如何做这部分。必须在Mobx中更深入地挖掘)
对于我的情况,这是纯 redux/mobx 比较中的利弊。
优点:
- 不那么冗长
- 没有要继承的模型(如在 redux-orm 中)
- 性能已经过评估(所以我几乎不知道我会去哪里)
- 不要在类模型中写"opinited"reducer(只是突变器)
缺点:
- 不知道如何实现重做/撤消(或游戏开发中的抖动缓冲区)
- 它似乎不像 redux 那样在眨眼间拥有整个状态的"树"(对我来说,这是 redux 的杀手锏)
我想补充一点,如果你要使用 Redux,你根本不会在类中存储状态。在 Redux 中,此逻辑将在化简器中描述,化简器将在普通对象而不是类实例上运行。您将保持数据规范化,以便每个实体都按其 ID 保存在对象映射中,并且对子实体的任何引用都将是 ID 数组,而不是实际引用。然后,您将编写选择器来重建您关心的渲染数据部分。
您可能会发现此讨论以及以下两个示例很有帮助:
- 购物车
- 树视图
MobX 作者)。有关MobX问题的简短回答:
重做/撤消可以通过两种方式实现:
- 使用 flux 中的操作/调度程序模型:调度可序列化操作,并解释它们以更新状态模型。这样,您可以构建仅附加操作日志并在此基础上撤消/重做。
- 自动将状态模型序列化为状态历史记录(使用结构共享)。reactive-2015 演示很好地演示了这一点:https://github.com/mobxjs/mobx-reactive2015-demo/blob/master/src/stores/time.js。在此序列化期间,如果更易于处理,还可以生成增量修补程序而不是完整的状态树。
单状态树:
- 在MobX中,也应该有一个单一的事实来源。与Redux的主要区别在于它没有规定你要存储它。它也不会强迫你拥有一棵树。图表也可以做得很好。通过利用 mobx.toJson 或使用上一点 2 的解决方案,可以轻松获取该图的快照。重做/撤消。
- 若要确保所有内容都在一个连接的图形中(例如,只需创建一个指向玩家和世界集合的根状态对象)。但与 Redux 不同的是,您不必从那里开始规范化。世界只能直接引用玩家,反之亦然。在 reactive-2015 演示中也创建了一个单一状态根对象:https://github.com/mobxjs/mobx-reactive2015-demo/blob/master/src/stores/domain-state.js
你可能想看看redux-orm,它已经做到了这一点。 它在 Redux 状态下的实际纯对象数据上提供了一个很好的类似模型的外观,并且可以很好地处理关系数据。