Redux 和 Mobx的选择问题:让你不再困惑!
我在去年大量的使用了Redux,但我最近都在使用Mobx来做状态(state)管理。似乎现在社区里关于该选什么来替代Redux很自然地成为了一件困惑的事。开发者不确定该选择哪种解决方案。这个问题并不只是出现在Redux与Mobx上。无论何时,只要存在选择,人们就会好奇最好的解决问题的方式是什么。我现在写的这些是为了解决Redux和Mobx这两个状态管理库之间的困惑。
大部分的文章都用React来介绍Mobx和Redux的用法。但是在大部分情况下你都可以将React替换成Angular、Vue或其他。
在2016年年初的时候我用React+Redux写了一个相当大的应用。在我发现可以使用Mobx替代Redux时,我花时间将应用从Redux重构成了Mobx。现在我可以非常自在的使用它俩并且解释它俩的用法。
这篇文章将要讲什么呢?如果你不打算看这么长的文章(TLDR:toolong,didn'tread(查看此链接请自备梯子)),你可以看下目录。但我想给你更多细节:第一,我想简单地回顾状态管理库为我们解决了什么问题。毕竟我们写React时只用setState()或写其他SPA框架时用setState()类似的方法一样也可以做的不错。第二,我会大致的说下它们之间的相同之处和不同之处。第三,我会给React生态初学者指明怎样学习React的状态管理。友情提醒:在你深入Mobx和Redux之前,请先使用setState()。最后,如果你已经有一个使用了Mobx或Redux的应用,我将会就如何从其中一个状态管理库重构到另一个给你更多我的理解。
目录
我们要解决的是什么问题?
Mobx和Redux的不同?
React状态管理的学习曲线
尝试另一个状态管理方案?
最后思考
我们要解决的是什么问题?
所有人都想在应用中使用状态管理。但它为我们解决了什么问题?很多人开始一个小应用时就已经引入一个状态管理库。所有人都在谈论Mobx和Redux,不是吗?但大部分应用在一开始的时候并不需要大型的状态管理。这甚至是危险的,因为这部分人将无法体验Mobx和Redux这些库所要解决的问题。
如今的现状是要用组件(components)来构建一个前端应用。组件有自己的内部状态。举个栗子,在React中上述的本地状态是用this.state和setState()来处理。但本地状态的状态管理在膨胀的应用中很快会变得混乱,因为:
一个组件需要和另一个组件共享状态
一个组件需要改变另一个组件的状态
到一定程度时,推算应用的状态将会变得越来越困难。它就会变成一个有很多状态对象并且在组件层级上互相修改状态的混乱应用。在大部分情况下,状态对象和状态的修改并没有必要绑定在一些组件上。当你把状态提升时,它们可以通过组件树得到。
所以,解决方案是引入状态管理库,比如:Mobx或Redux。它提供工具在某个地方保存状态、修改状态和更新状态。你可以从一个地方获得状态,一个地方修改它,一个地方得到它的更新。它遵循单一数据源的原则。这让我们更容易推断状态的值和状态的修改,因为它们与我们的组件是解耦的。
像Redux和Mobx这类状态管理库一般都有附带的工具,例如在React中使用的有react-redux和mobx-react,它们使你的组件能够获得状态。一般情况下,这些组件被叫做容器组件(containercomponents),或者说的更加确切的话,就是连接组件(connectedcomponents)。只要你将组件升级成连接组件,你就可以在组件层级的任何地方得到和更改状态。
Mobx和Redux的不同?
在我们深入了解Redux和Mobx的不同之前,我想先谈谈它们之间的相同之处。
这两个库都是用来管理JavaScript应用的状态。它们并不一定要跟React绑定在一起,它们也可以在AngularJs和VueJs这些其他库里使用。但它们与React的理念结合得非常好。
如果你选择了其中一个状态管理方案,你不会感到被它锁定了。因为你可以在任何时候切换到另一个解决方案。你可以从Mobx换成Redux或从Redux换成Mobx。我下面会展示如何能够做到。
DanAbramov的Redux是从flux架构派生出来的。和flux不同的是,Redux用单一store而不是多个store来保存state,另外,它用纯函数替代dispatcher来修改state,如果你对flux不熟并且没接触过状态管理,不要被这段内容所烦恼。
Redux被FP(函数式编程)原则所影响。FP可以在JavaScript中使用,但很多人有面向对象语言的背景,比如Java。他们在刚开始的时候很难适应函数式编程的原则。这就是为什么对于初学者来说Mobx可能更加简单。
既然Redux拥抱FP,那它使用的就是纯函数。一个接受输入并返回输出并且没有其他依赖的纯函数。一个纯函数在相同的输入下输出总是相同而且没有任何副作用。
(state,action)=>newState
你的Reduxstate是不可变的,你应该总是返回一个新的state而不是修改原state。你不应该执行state的修改或依据对象引用的更改。
//don'tdothisinRedux,becauseitmutatesthearray functionaddAuthor(state,action){ returnstate.authors.push(action.author); } //stayimmutableandalwaysreturnanewobject functionaddAuthor(state,action){ return[...state.authors,action.author]; }
最后,在Redux的习惯用法里,state的格式是像数据库一样标准化的。实体之间只靠id互相引用,这是最佳实践。虽然不是每个人都这样做,你也可以使用normalizr来使state标准化。标准化的state让你能够保持一个扁平的state和保持实体为单一数据源。
{ post:{ id:'a', authorId:'b', ... }, author:{ id:'b', postIds:['a',...], ... } }
MichelWeststrate的Mobx则是受到面向对象编程和响应式编程的影响。它将state包装成可观察的对象,因此你的state就有了Observable的所有能力。state数据可以只有普通的setter和getter,但observable让我们能在数据改变的时候得到更新的值。
Mobx的state是可变的,所以你直接的修改state:
functionaddAuthor(author){ this.authors.push(author); }
除此之外,state实体保持嵌套的数据结构来互相关联。你不必标准化state,而是让它们保持嵌套。
{ post:{ id:'a', ... author:{ id:'b', ... } } }
单store与多stores
在Redux中,你将所有的state都放在一个全局的store。这个store对象就是你的单一数据源。另一方面,多个reducers允许你修改不可变的state。
Mobx则相反,它使用多stores。和Redux的reducers类似,你可以在技术层面或领域进行分治。也许你想在不同的stores里保存你的领域实体,但仍然保持对视图中state的控制。毕竟你配置state是为了让应用看起来更合理。
从技术层面来说,你一样可以在Redux中使用多个stores。没有人强迫你只能只用一个store。但那不是Redux建议的用法。因为那违反了最佳实践。在Redux中,你的单store通过reducers的全局事件来响应更新。
如何使用?
你需要跟随下面的代码学习使用Redux,首先在全局state上新增一个user数组。你可以看到我通过对象扩展运算符来返回一个新对象。你同样可以在ES6(原文为ES5,实际是应该是ES6)中使用Object.assign()来操作不可变对象。
constinitialState={ users:[ { name:'Dan' }, { name:'Michel' } ] }; //reducer functionusers(state=initialState,action){ switch(action.type){ case'USER_ADD': return{...state,users:[...state.users,action.user]}; default: returnstate; } } //action {type:'USER_ADD',user:user};
你必须使用dispatch({type:'USER_ADD',user:user});来为全局state添加一个新user。
在Mobx中,一个store只管理一个子state(就像Redux中管理子state的reducer),但你可以直接修改state。
@observable让我们可以观察到state的变化。
classUserStore{ @observableusers=[ { name:'Dan' }, { name:'Michel' } ]; }
现在我们就可以调用store实例的方法:userStore.users.push(user);。这是一种最佳实践,虽然使用actions去操作state的修改更加清楚明确。
classUserStore{ @observableusers=[ { name:'Dan' }, { name:'Michel' } ]; @actionaddUser=(user)=>{ this.users.push(user); } }
在Mobx中你可以加上useStrict()来强制使用action。现在你可以调用store实例上的方法:userStore.addUser(user);来修改你的state。
你已经看到如何在Redux和Mobx中更新state。它们是不同的,Redux中state是只读的,你只能使用明确的actions来修改state,Mobx则相反,state是可读和写的,你可以不使用actions直接修改state,但你可以useStrict()来使用明确的actions。
React状态管理的学习曲线
React应用广泛使用Redux和Mobx。但它们是独立的状态管理库,可以运用在除React的任何地方。它们的互操作库让我们能简单的连接React组件。Redux+React的react-redux和MobX+React的mobx-react。稍后我会说明它俩如何在React组件树中使用。
在最近的讨论中,人们在争论Redux的学习曲线。这通常发生在下面的情境中:想使用Redux做状态管理的React初学者。大部分人认为React和Redux本身都有颇高的学习曲线,两者结合的话会失控。一个替代的选择就是Mobx,因为它更适合初学者。
然而,我会建议React的初学者一个学习状态管理的新方法。先学习React组件内部的状态管理功能。在React应用,你首先会学到生命周期方法,而且你会用setState()和this.state解决本地的状态管理。我非常推荐上面的学习路径。不然你会在React的生态中迷失。在这条学习路径的最后,你会认识到组件内部管理状态难度越来越大。毕竟那是TheRoadtolearnReact书里如何教授React状态管理的方法。
现在我们重点讨论Redux和Mobx为我们解决了什么问题?它俩都提供了在组件外部管理应用状态的方法。state与组件相互解耦,组件可以读取state,修改state,有新state时更新。这个state是单一数据源。
现在你需要选择其中一个状态管理库。这肯定是要第一时间解决的问题。此外,在开发过相当大的应用之后,你应该能很自如使用React。
初学者用Redux还是Mobx?
一旦你对React组件和它内部的状态管理熟悉了,你就能选择出一个状态管理库来解决你的问题。在我两个库都用过后,我想说Mobx更适合初学者。我们刚才已经看到Mobx只要更少的代码,甚至它可以用一些我们现在还不知道的魔法注解。
用Mobx你不需要熟悉函数式编程。像“不可变”之类的术语对你可能依然陌生。函数式编程是不断上升的范式,但对于大部分JavaScript开发者来说是新奇的。虽然它有清晰的趋势,但并非所有人都有函数式编程的背景,有面向对象背景的开发者可能会更加容易适应Mobx的原则。
注:Mobx可以很好的在React内部组件状态管理中代替setState,我还是建议继续使用setState()管理内部状态。但链接文章很清楚的说明了在React中用Mobx完成内部状态管理是很容易的。
规模持续增长的应用
在Mobx中你改变注解过的对象,组件就会更新。Mobx比Redux使用了更多的内部魔法实现,因此在刚开始的时候只要更少的代码。有Angular背景的会觉得跟双向绑定很像。你在一个地方保存state,通过注解观察state,一旦state修改组件会自动的更新。
Mobx允许直接在组件树上直接修改state。
//componentstore.users.push(user)}/>
更好的方式是用store的@action。
//componentstore.addUser(user)}/> //store @actionaddUser=(user)=>{ this.users.push(user); }
用actions修改state更加明确。上面也提到过,有个小功能可以强制的使用actions修改state。
//rootfile import{useStrict}from'mobx'; useStrict(true);
这样的话第一个例子中直接修改store中的state就不再起作用了。前面的例子展示了怎样拥抱Mobx的最佳实践。此外,一旦你只用actions,你就已经使用了Redux的约束。
在快速启动一个项目时,我会推荐使用Mobx,一旦应用开始变得越来越大,越来越多的人开发时,遵循最佳实践就很有意义,如使用明确的actions。这是拥抱Redux的约束:你永远不能直接修改state,只能使用actions。
迁移到Redux
一旦应用开始变得越来越大,越来越多的人开发时,你应该考虑使用Redux。它本身强制使用明确的actions修改state。action有type和payload参数,reducer可以用来修改state。这样的话,一个团队里的开发人员可以很简单的推断state的修改。
//reducer (state,action)=>newState
Redux提供状态管理的整个架构,并有清晰的约束规则。这是Redux的成功故事。
另一个Redux的优势是在服务端使用。因为我们使用的是纯JavaScript,它可以在网络上传输state。序列化和反序列化一个state对象是直接可用的。当然Mobx也是一样可以的。
Mobx是无主张的,但你可以通过useStrict()像Redux一样使用清晰的约束规则。这就是我为什么没说你不能在扩张的应用中使用Mobx,但Redux是有明确的使用方式的。而Mobx甚至在文档中说:“Mobx不会告诉你如何组织代码,哪里该存储state或怎么处理事件。”所以开发团队首先要确定state的管理架构。
状态管理的学习曲线并不是很陡峭。我们总结下建议:React初学者首先学习恰当的使用setState()和this.state。一段时间之后你将会意识到在React应用中仅仅使用setState()管理状态的问题。当你寻找解决方案时,你会在状态管理库Mobx或Redux的选择上犹豫。应该选哪个呢?由于Mobx是无主张的,使用上可以和setState()类似,我建议在小项目中尝试。一旦应用开始变得越来越大,越来越多的人开发时,你应该考虑在Mobx上实行更多的限制条件或尝试使用Redux。我使用两个库都很享受。即使你最后两个都没使用,了解到状态管理的另一种方式也是有意义的。
尝试另一个状态管理方案?
你可能已经使用了其中一个状态管理方案,但是想考虑另一个?你可以比较现实中的Mobx和Redux应用。我把所有的文件修改都提交到了一个PullRequest。在这个PR里,项目从Redux重构成了Mobx,反之亦然,你可以自己实现。我不认为有必要和Redux或Mobx耦合,因为大部分的改变是和其他任何东西解耦的。
你主要需要将Redux的Actions、ActionCreator、ActionTypes、Reducer、GlobalStore替换成Mobx的Stores。另外将和React组件连接的接口react-redux换成mobx-react。presenter+containerpattern依然可以执行。你仅仅还要重构容器组件。在Mobx中可以使用inject获得store依赖。然后store可以传递substate和actions给组件。Mobx的observer确保组件在store中observable的属性变化时更新。
import{observer,inject}from'mobx-react'; ... constUserProfileContainer=inject( 'userStore' )(observer(({ id, userStore, })=>{ return(); }));
Redux的话,你使用mapStateToProps和mapDispatchToProps传递substate和actions给组件。
import{connect}from'react-redux'; import{bindActionCreators}from'redux'; ... functionmapStateToProps(state,props){ const{id}=props; constuser=state.users[id]; return{ user, }; } functionmapDispatchToProps(dispatch){ return{ onUpdateUser:bindActionCreators(actions.updateUser,dispatch), }; } constUserProfileContainer=connect(mapStateToProps,mapDispatchToProps)(UserProfile);
这有一篇怎样将Redux重构为Mobx指南。但就像我上面说过的,反过来一样也是可以的。一旦你选择了一个状态管理库,你会知道那并没有什么限制。它们基本上是和你的应用解耦的,所以是可以替换的。
最后思考
每当我看ReduxvsMobx争论下的评论时,总会有下面这条:“Redux有太多的样板代码,你应该使用Mobx,可以减少xxx行代码”。这条评论也许是对的,但没人考虑得失,Redux比Mobx更多的样板代码,是因为特定的设计约束。它允许你推断应用状态即使应用规模很大。所以围绕state的仪式都是有原因的。
Redux库非常小,大部分时间你都是在处理纯JavaScript对象和数组。它比Mobx更接近vanillaJavaScript。Mobx通过包装对象和数组为可观察对象,从而隐藏了大部分的样板代码。它是建立在隐藏抽象之上的。感觉像是出现了魔法,但却很难理解其内在的机制。Redux则可以简单通过纯JavaScript来推断。它使你的应用更简单的测试和调试。
另外,我们重新回到单页应用的最开始来考虑,一系列的单页应用框架和库面临着相同的状态管理问题,它最终被flux模式解决了。Redux是这个模式的成功者。
Mobx则又处在相反的方向。我们直接修改state而没有拥抱函数式编程的好处。对一些开发者来说,这让他们觉得像双向绑定。一段时间之后,由于没有引入类似Redux的状态管理库,他们可能又会陷入同样的问题。状态管理分散在各个组件,导致最后一团糟。
使用Redux,你有一个既定的模式组织代码,而Mobx则无主张。但拥抱Mobx最佳实践会是明智的。开发者需要知道如何组织状态管理从而更好的推断它。不然他们就会想要直接在组件中修改它。
两个库都非常棒。Redux已经非常完善,Mobx则逐渐成为一个有效的替代。
总结
以上就是本文关于在Redux和Mobx之间如何选择的问题,相信大家阅读过本文之后应该有了清晰的了解。
谢谢大家对本站的支持!