如何在 Redux 中使用 class 模型(使用 Mobx 选项)
How to use class model with Redux (with a Mobx option)
编辑:我最终选择了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(未测试)
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 的方式。
缺点:
- 所有逻辑都从模型分散到另一个地方。我不喜欢倍增文件。但也许这不是真正的问题?
- Redux 说逻辑必须放在动作中,而不是在 reducer 中。
- 状态占用两倍的内存。我看到 immutables.js 可以优化这个,但我不确定。
Take 2:在 redux 状态树中只存储 ids
{
players:[
001,
002
//...
]
}
这也可以通过在每个 setter
中使用 dispatch
并在每个 setter
之后发送一个动作来完成
// setter
setHealth(hp){
this.life += hp;
dispatch({type:"PLAYER_UPDATED", data:{id:this.id})
}
当新的树状态改变时。我调用 mapStateToProps
和 WorldCollection.getPlayer()
来检索正确的实例并将其属性映射到视图。
优点:
- 通过不将逻辑放入 reducer 来尊重 Redux 方式
- 不是“重复状态”(如果Immutables.js不能优化这个)
- 逻辑在模型中(对我来说更有意义)
缺点:
- Redux 状态并不代表整个状态
我希望我没有过度简化案例。我的意思是澄清 if/how redux 可以与某些 class 模型一起使用。
练习 3:使用 Mobx.js instead/with Redux
--- 非常前实验性 ---
我一周前发现了 Mobx.js,它的 simplicity/perf 吸引了我。
我想我们可以观察每个 class 成员(它们一起构成应用程序状态)
@observable life; //int
@observable lvl; //int
@observable buffs; //[objects]
@observable debuffs; //[objects]
@observable inventory; //[objects]
其他地方有一个 class 构建状态树,也许 Redux 在这里有意义? (注意我不知道如何做这部分。必须在 Mobx 中更深入地挖掘)
这是 pros/cons 的纯粹 redux/mobx 比较 我的案例。
优点:
- 不那么冗长
- 没有可继承的模型(就像在 redux-orm 中一样)
- 性能已经评估(所以我几乎不知道我会去哪里)
- 不要在 class 模型中编写“固执己见”的 reducer(只是修改器)
缺点:
- 不知道如何实现 redo/undo(或游戏开发中的抖动缓冲区)
- 似乎不像redux中的“树”那样一眨眼就拥有了整个状态(对我来说这是redux的杀手级功能)
您可能想看看 redux-orm,它几乎已经这样做了。它为 Redux 状态中的实际普通对象数据提供了一个类似模型的外观,并且可以很好地处理关系数据。
(MobX 作者)。关于 MobX 问题的简短回答:
重做/撤销可以通过两种方式实现:
- 使用 flux 中的动作/调度程序模型:调度可序列化的动作,并解释它们以更新状态模型。通过这种方式,您可以构建一个仅附加的操作日志,并在此基础上进行撤消/重做。
- 自动将您的状态模型序列化为状态历史(使用结构共享)。 reactive-2015 演示很好地演示了这一点:https://github.com/mobxjs/mobx-reactive2015-demo/blob/master/src/stores/time.js。在此序列化期间,如果更容易处理,您还可以生成增量补丁而不是完整的状态树。
单状态树:
- 在 MobX 中也应该有一个单一的事实来源。与 Redux 的主要区别在于它没有规定你要存储它。它也不强迫你有一棵树。图表也可以。获取该图的快照可以通过利用 mobx.toJson 或使用重做/撤消之前第 2 点的解决方案来简单地完成。
- 为了确保所有内容都在一个连接图中(您喜欢),只需创建一个指向玩家和世界集合的根状态对象(例如)。但与 Redux 不同的是,您不必从那里开始规范化。 World 可以直接引用玩家,反之亦然。在 reactive-2015 演示中也创建了一个状态根对象:https://github.com/mobxjs/mobx-reactive2015-demo/blob/master/src/stores/domain-state.js
我想补充一点,如果你要使用 Redux 你根本不会在 classes 中存储状态。在 Redux 中,这种逻辑将在缩减器中描述,缩减器将对普通对象而不是 class 实例进行操作。您将保持数据规范化,以便每个实体都通过其 ID 保存在对象映射中,并且对子实体的任何引用都将是 ID 数组而不是真正的引用。然后,您将编写选择器来重建您关心渲染的数据部分。
您可能会发现 this discussion 有帮助,以及以下两个示例:
编辑:我最终选择了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(未测试)
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 的方式。
缺点:
- 所有逻辑都从模型分散到另一个地方。我不喜欢倍增文件。但也许这不是真正的问题?
- Redux 说逻辑必须放在动作中,而不是在 reducer 中。
- 状态占用两倍的内存。我看到 immutables.js 可以优化这个,但我不确定。
Take 2:在 redux 状态树中只存储 ids
{
players:[
001,
002
//...
]
}
这也可以通过在每个 setter
中使用 dispatch
并在每个 setter
// setter
setHealth(hp){
this.life += hp;
dispatch({type:"PLAYER_UPDATED", data:{id:this.id})
}
当新的树状态改变时。我调用 mapStateToProps
和 WorldCollection.getPlayer()
来检索正确的实例并将其属性映射到视图。
优点:
- 通过不将逻辑放入 reducer 来尊重 Redux 方式
- 不是“重复状态”(如果Immutables.js不能优化这个)
- 逻辑在模型中(对我来说更有意义)
缺点:
- Redux 状态并不代表整个状态
我希望我没有过度简化案例。我的意思是澄清 if/how redux 可以与某些 class 模型一起使用。
练习 3:使用 Mobx.js instead/with Redux
--- 非常前实验性 ---
我一周前发现了 Mobx.js,它的 simplicity/perf 吸引了我。
我想我们可以观察每个 class 成员(它们一起构成应用程序状态)
@observable life; //int
@observable lvl; //int
@observable buffs; //[objects]
@observable debuffs; //[objects]
@observable inventory; //[objects]
其他地方有一个 class 构建状态树,也许 Redux 在这里有意义? (注意我不知道如何做这部分。必须在 Mobx 中更深入地挖掘)
这是 pros/cons 的纯粹 redux/mobx 比较 我的案例。
优点:
- 不那么冗长
- 没有可继承的模型(就像在 redux-orm 中一样)
- 性能已经评估(所以我几乎不知道我会去哪里)
- 不要在 class 模型中编写“固执己见”的 reducer(只是修改器)
缺点:
- 不知道如何实现 redo/undo(或游戏开发中的抖动缓冲区)
- 似乎不像redux中的“树”那样一眨眼就拥有了整个状态(对我来说这是redux的杀手级功能)
您可能想看看 redux-orm,它几乎已经这样做了。它为 Redux 状态中的实际普通对象数据提供了一个类似模型的外观,并且可以很好地处理关系数据。
(MobX 作者)。关于 MobX 问题的简短回答:
重做/撤销可以通过两种方式实现:
- 使用 flux 中的动作/调度程序模型:调度可序列化的动作,并解释它们以更新状态模型。通过这种方式,您可以构建一个仅附加的操作日志,并在此基础上进行撤消/重做。
- 自动将您的状态模型序列化为状态历史(使用结构共享)。 reactive-2015 演示很好地演示了这一点:https://github.com/mobxjs/mobx-reactive2015-demo/blob/master/src/stores/time.js。在此序列化期间,如果更容易处理,您还可以生成增量补丁而不是完整的状态树。
单状态树:
- 在 MobX 中也应该有一个单一的事实来源。与 Redux 的主要区别在于它没有规定你要存储它。它也不强迫你有一棵树。图表也可以。获取该图的快照可以通过利用 mobx.toJson 或使用重做/撤消之前第 2 点的解决方案来简单地完成。
- 为了确保所有内容都在一个连接图中(您喜欢),只需创建一个指向玩家和世界集合的根状态对象(例如)。但与 Redux 不同的是,您不必从那里开始规范化。 World 可以直接引用玩家,反之亦然。在 reactive-2015 演示中也创建了一个状态根对象:https://github.com/mobxjs/mobx-reactive2015-demo/blob/master/src/stores/domain-state.js
我想补充一点,如果你要使用 Redux 你根本不会在 classes 中存储状态。在 Redux 中,这种逻辑将在缩减器中描述,缩减器将对普通对象而不是 class 实例进行操作。您将保持数据规范化,以便每个实体都通过其 ID 保存在对象映射中,并且对子实体的任何引用都将是 ID 数组而不是真正的引用。然后,您将编写选择器来重建您关心渲染的数据部分。
您可能会发现 this discussion 有帮助,以及以下两个示例: