本文是受到@mwestrase许多文章的启发, 以及经过几个星期将一个大型的Backbone应用程序重构为React + MobX应用程序,是将Mobx添加到一个普通的React项目的“衣钵传人”。 或许这不是构建Mobx应用程序的 最佳方式 ,但这种方式到目前为止一直在我的项目中很好的运作。
设计本架构的目标是:
样板最小化
使应用程序成为一个状态机
灵活移动元素
我们将通过React上下文和MobX的出色的inject
函数来实现样板最小化。这样的组合使数据集无需进行任何接线和props传递就可以在应用程序的任何地方使用。 如果组件需要访问状态机, 注入到store中。
你的UI设计师大可以根据其想法重新调整页面,而你所要做的只是更改组件的位置。 除非业务逻辑本身改变,否则不需要重新接线业务逻辑。 对我而言,这曾经是React的一个难题。 使用这种方法,你的组件变得真正独立,你可以做任何你想要的。
这非常有趣,我甚至为此早早来上班! 如果你了解我,你会知道我有多喜欢过属于自己的早晨。
这是如何完成的
通过上下文和注入给了我们灵活性,并消除样板。 那么状态机呢?
我们把MobX的store视为应用程序中的唯一真实来源,并且我们将把actions置于其中。通过actions改变状态,并可以在任何地方调用。将它们放在store中以减少样板,并确保所有组件都可以访问它们,这会让你可以把整个状态机看成一个单文件。
这也会使你的应用程序更容易测试。 如果你的状态机工作正常,那么就一切正常。 这是因为:
在严格模式下MobX保证状态只会在actions中改变
React保证DOM是一个纯粹的状态表达式
是的,MobX运作通过可变状态,改变观察者以及所有有趣的东西。 如果在过去几年你一直听言关于不可变状态的函数式编程的倡导,那么这听起来很糟糕。
但是你知道什么才是酷的? 无论如何,你可以获得所有的好处。 MobX将你的状态变化包装在getter和setter中,但它也可以支持那些具有不可变数据结构的应用程序,并且actions可以为时间旅行调试器建立一个更新日志。
这可能会是一个有趣的项目。 我可以使用MobX的时间旅行调试器?
model是另一个难题。
model表示整个数据结构中的特定对象。 MobX的store会关注你的整个应用程序的状态;model是为了说明一个特定的React组件所关注的特定实例。
这听起来model应该在组件状态中,对吧? 但这是一个糟糕的主意,因为这样会让你的东西更难测试并且会打破状态机的理想状态。
model也是存放与后端交互的actions的好地方。 诸如保存,获取,更新。
让我们构建一盒柿子
你可以把它想象成伪代码。
让我们构建一盒 柿子。 现在我的厨房柜台上就有一盒,因为柿子就长在我女朋友的妈妈的后院。 谁知道? ¯(ツ)/¯
盒子可以被打开或关闭。盒子里有柿子时,可以用胶带密封它, 它的状态机看起来像这样:
一旦盒子被打开, 只要你想,你可以放入或取出尽可能多的柿子。你可以从任何N kaki
状态关闭这个盒子,但你必须通过打开它来放入或取出柿子。 但只有在关闭时才能密封,只有在启封时才能打开。
盒子,model
作为MobX的store和model,你的盒子可能像这样:
// src/models/Box.jsimport { observable, computed, action, extendObservable } from 'mobx';export class Box { @observable sealed = true; @observable closed = true; @observable kakis = []; constructor(store, initialState) { this.store = store; extendObservable(this, initialState); } @computed get canSeal() { return this.closed; } @computed get canOpen() { return !this.sealed; } @computed get canManipulatekakis() { return !this.closed; } @action addkaki() { this.kakis.push(new kaki()); } @action takekaki() { this.kakis.pop(); } @action open() { if (this.canOpen) { this.opened = true; } } @action close() { this.closed = true; } @action seal() { if (this.canSeal) { this.sealed = true; } } @action unseal() { this.sealed = false; }}
@ observable
是一个MobX装饰器,MobX会使得属性可观察。extendObservable
是在对象上设置许多可观察值的一种方便的方法。@ computed
会标记一些纯粹从状态派生出的属性,MobX可以记忆它们。@action
会将方法标记为动作。
看看那张状态机的图片。观察者和计算属性放在一起是圆圈(状态),动作是箭头(状态之间的转换)。
主应用程序组件
为了一个盒子做了很多工作吧? 看看我们把它放在React组件里会发生什么:
// src/components/App.jsimport { Provider } from 'mobx-react';import { Box } from './models/Box';// would normally go in src/stores/...class MainStore { @observable box = null; @action getBoxFromMail() { this.box = new Box({ sealed: true, closed: true, kakis: [new kaki(), new kaki()] }); }}class App extends Component { mainStore = new MainStore(); render() { const mainStore = this.mainStore; return ( <Provider mainStore={mainStore}> <div> <Button onClick={mainStore.getBoxFromMail.bind(mainStore)}> Get Mail </Button> <Box /> </div> </Provider> ); }}
我们建立了一个新的MainStore
,它保存了我们应用程序的主要状态。诸如是否从邮箱取出一盒柿子,柜台是否是黑色,冰箱是否在工作之类的。
在render函数中,我们使用Provider
将mainStore
添加到React上下文。 如果需要,Provider
中的任何组件都可以访问mainStore
。
将actions与store和model捆绑在一起好处是,你可以在onClick
处理程序中使用它们。 这意味着你的大部分组件可以是无状态的功能组件。
盒子, React组件
我来给你展示。
// src/components/Box.jsimport { inject, observer } from 'mobx-react';const SealedOrOpened = observer(({ box }) => ( if (box.sealed) { return 'Sealed and Closed'; }else if (!box.sealed && !box.opened) { return 'You should open the box'; }else{ return 'Take some kakis!'; }));// this helper normally goes somewhere elseconst If = ({ cond, children }) => cond ? children : null;const Box = inject('mainStore')(observer(({ mainStore }) => { const box = mainStore.box; return ( <div> <SealedOrOpened box={box} /> <If cond={box.canOpen}> <Button onClick={box.open.bind(box)}>Open box</Button> </If> <If cond={box.opened}> <ul> {box.kakis.map(kaki => <kaki />)} </ul> <Button onClick={box.takekaki.bind(box)}> Take a kaki </Button> </If> </div> );));
这就是本质上的Box
组件。它会渲染一些文本,告诉你如何处理这个盒子,并给你一个打开它的按钮。是的,我知道应该有一个按钮来取下胶带。
一旦你打开盒子,就会出现一个柿子列表和一个按钮,来把它们一个接一个地取出来。
请注意,Box
是一个函数式无状态的组件。 你可以这样做,因为所有的状态和所有的操作都在Box
,即MobX的model中。 没有必要在本地建立点击处理程序。
您可能还会注意到,我们已经将inject('mainStore')
注入到Box组件中。这使得我们可以将组件移动到Provider
内的DOM树中的任何位置,并且它将继续工作。不需要任何的修改。
❤️ 总结
你的UI设计师会喜欢这种灵活性,你的PM会决定你的速度超乎想象。
哦,那个observer
呢? 它可以确保你的组件在状态更改时重新渲染。
下周,我要写一篇关于将这些更改保留到某种后端的文章。 我仍在努力如何使这部分省事且整洁。
也许我应该写一本关于React和MobX构建Web应用程序的书? 如果你感兴趣,请告诉我.
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。