jorgebucaran / hyperapp

1kB-ish JavaScript framework for building hypertext applications
MIT License
19.06k stars 781 forks source link

Mobx support? #103

Closed ghost closed 7 years ago

ghost commented 7 years ago

So, we have redux/elm like state management. But, what about mobx?

Here is their user guide. Here is a 10 minute interactive tutorial.

Mobx picture

I used to write with Redux, but Mobx changed everything for me. Besides having data react automatically, the most important thing for me was being able to use classes with real references (no normalizing of data required). Also, Typescript... is amazing (Mobx is written in it).

jorgebucaran commented 7 years ago

But, what about mobx?

Hi @thinkpadder1 👋

HyperApp's is influenced by the Elm Architecture, hence the suitable comparison to Redux. In HyperApp, the model is theoretically immutable, components are stateless, pure functions and actions are a mixture of reducers (old state + data -> new state) and effects (reactions in Mobx).

Mobx seems to favor object-oriented programming, using classes/prototypes and stateful components. Use of observables, decorators and TypeScript seems also popular.

At a glance, Mobx seems entirely different to HyperApp's model, so I have no idea how it could be integrated with Mobx. I'd be interested in hearing your ideas, though. How would the API look like if Mobx was built in?

Also, just wondering, how large is Mobx?

jorgebucaran commented 7 years ago

I looked at their examples, found a simple countdown timer example and made the same thing in HyperApp. Let's see how they compare:

Countdown Timer: HyperApp ```html ``` ```jsx const { h, app } = hyperapp /** @jsx h */ const pad = n => n < 10 ? "0" + n : n const humanizeTime = t => { const hours = t / 3600 >> 0 const minutes = (t - hours * 3600) / 60 >> 0 const seconds = (t - hours * 3600) - minutes * 60 >> 0 return `${hours} : ${pad(minutes)} : ${pad(seconds)}` } const initialTime = 10 app({ model: { time: initialTime, pause: true }, reducers: { drop: model => ({ time: model.time - 1 }), pause: model => ({ pause: !model.pause }), reset: model => ({ time: initialTime }) }, effects: { step: (model, actions) => { if (model.time === 0) { actions.reset() actions.pause() } else if (!model.pause) { actions.drop() } } }, subscriptions: [ (_, {step}) => setInterval(step, 1000) ], view: (model, actions) =>
{humanizeTime(model.time)}
, }) ``` [View online](http://codepen.io/jbucaran/pen/evOZLv?editors=0010)
Countdown Timer: React+Mobx ```html ``` ```jsx mobx.useStrict(true); var countdownTimerFactory = function(durationMilliseconds, options) { var intervalID; var timer = { id: _.uniqueId("countdownTimer_"), originalMilliseconds: durationMilliseconds }; var settings = _.assign( { interval: 10, runTime: 0, resetOnComplete: true }, options ); mobx.extendObservable(timer, { durationAsMilliseconds: timer.originalMilliseconds, isTimerRunning: false, get isComplete() { return timer.durationAsMilliseconds <= 0; }, get display() { return _.padStart(timer.minutesRemaining, 2, 0) + " : " + _.padStart(timer.secondsRemaining, 2, 0); }, get durationAsDate() { return new Date(timer.durationAsMilliseconds); }, get millisecondsRemaining() { return _.round(timer.durationAsDate.getUTCMilliseconds(), 2); }, get secondsRemaining() { return timer.durationAsDate.getUTCSeconds(); }, get minutesRemaining() { return timer.durationAsDate.getUTCMinutes(); }, get hoursRemaining() { return timer.durationAsDate.getUTCHours(); }, get percentageComplete() { return 100 - _.round( timer.durationAsMilliseconds / timer.originalMilliseconds * 100, 2 ); }, startTimer: mobx.action("startTimer", function() { timer.isTimerRunning = true; }), stopTimer: mobx.action("stopTimer", function() { timer.isTimerRunning = false; }), reset: mobx.action("resetTimer", function() { timer.stopTimer(); timer.durationAsMilliseconds = timer.originalMilliseconds; }) }); mobx.autorun("countDownTimer", function() { if (timer.isTimerRunning) { intervalID = window.setInterval( function() { mobx.runInAction("timer tick", function() { timer.durationAsMilliseconds -= settings.interval; if (timer.isComplete) { timer.isTimerRunning = false; } }); }, settings.interval ); } else if (intervalID) { if (settings.resetOnComplete && timer.isComplete) { timer.reset(); } window.clearInterval(intervalID); } }); return timer; }; var Main = mobxReact.observer(function(props) { var timer = props.timer; return React.DOM.div( null, React.createElement(mobxDevtools.default), React.createElement(timerWithBar, { timer: timer }), React.createElement(timerControl, { timer: timer }, timerControl) ); }); var timerControl = mobxReact.observer( React.createClass({ render: function() { var timer = this.props.timer; var button; if (timer.isTimerRunning) { button = React.DOM.button( { onClick: this.handlePause }, "pause" ); } else { button = React.DOM.button( { onClick: this.handleStart }, "start" ); } return React.DOM.div(null, button); }, handleStart: function(event) { event.preventDefault(); this.props.timer.startTimer(); }, handlePause: function(event) { event.preventDefault(); this.props.timer.stopTimer(); } }) ); var timerWithBar = mobxReact.observer(function(props) { var timer = props.timer; return React.DOM.div( { className: "active-blind" }, React.createElement(timerPercentageCompleteRenderer, { timer: timer }), React.createElement(timerRenderer, { timer: timer }) ); }); var timerPercentageCompleteRenderer = mobxReact.observer(function(props) { return React.DOM.div( { className: "progress-bar-container" }, React.DOM.div({ className: "progress-bar", style: { width: props.timer.percentageComplete + "%" } }) ); }); var timerRenderer = mobxReact.observer(function(props) { var timer = props.timer; return React.DOM.div(null, timer.display); }); var timer = countdownTimerFactory(10000); ReactDOM.render( React.createElement(Main, { timer: timer }), document.getElementById("mount") ); ``` [View online](https://jsfiddle.net/gh/get/library/pure/mattruby/mobx-examples/tree/master/react-examples/60-countdown-timer) > Mobx example taken from [mobx-examples/60-countdown-timer](https://github.com/mobxjs/mobx-examples/tree/master/react-examples/60-countdown-timer).

Comments

ghost commented 7 years ago

The timer example is much too simple. Mobx was originally made because the creator was working on a project with a lot of domain models (more than 500 for his application alone). Imagine trying to create that in Redux/Elm and with functional programming only.

Here is an example of a todo domain store (best practices).

jorgebucaran commented 7 years ago

@thinkpadder1 Sure. That was just one of the examples in Mobx examples repository.

It seems the link you've posted links to mostly documentation, so it's kind of hard to say anything or make any hard comparisons. I'd rather work on actual examples.

Do you have an example where Mobx really shines? I'd love to code it using HyperApp and see where that takes me.

And I agree with you, a countdown timer is much too simple, but that also begs the question, how come Mobx's implementation of it is so complex?

jorgebucaran commented 7 years ago

@thinkpadder1 Mobx is on the left.

mobx

EDIT: Update image to include HyperApp's vanilla implementation.

tunnckoCore commented 7 years ago

And why you deleted my comment? It is perfectly readable and is seen that Mobx absolutely doesn't fit well in elm-like project. It's not just it doesnt fit, it totally cant be compared - size and overhead at least.

ghost commented 7 years ago

@jbucaran That is literally the most convoluted example I have ever come across. No one writes their MobX applications like that. I can rewrite it if you would like.

Here is a full application by Formidable Labs:

jorgebucaran commented 7 years ago

@thinkpadder1 The example was taken directly from mobx's org, from the mobx-examples repo.

ghost commented 7 years ago

@jbucaran All those examples are written in ES5, without JSX, no transpiling... He was just showing that you could do it without ES6+, decorators, Typescript, etc.

jorgebucaran commented 7 years ago

@thinkpadder1 Thanks for the links, unfortunately, that's not what I asked for. I'm still agnostic about Mobx's superiority over Redux or TEA (The Elm Architecture).

I think the correct way to debate here, is not with words, but code. I'll ask again, please provide an example where Mobx shines (no hurries, if you can find one just post it here when you do) so that I (or anyone else) can try to implement its HyperApp's version and learn HyperApp's architecture's choice shortcomings.

All those examples are written in ES5 and without JSX... He was just showing that you could do it without ES6+, decorators, typescript etc.

Sure! That'd certainly improve things quite a bit, I know. 30%? 🤔. Still, I think it's clear, coming from them, the counter example was a rather poor choice to represent Mobx.

I can rewrite it if you would like.

That's one option, another is to find a better example that we can work with. I am pretty sure I'm missing something obvious about Mobx.

EDIT: Update image to include HyperApp's vanilla implementation.

ghost commented 7 years ago

@jbucaran How about this remote control app for Google Play Desktop? It has a lot of side effects and inputs.

jorgebucaran commented 7 years ago

@thinkpadder1 Thanks, that's better than docs 😅. I was kind of hoping for some more down to earth type of examples. Something like HyperApp's Examples.

Mobx's org still has other examples that I didn't have time to look at, so I'll start by rewriting some of those to HyperApp. Maybe I'll find something.

While writing the countdown timer, I noticed a few things which led to a few issues today:

The bottom line, all this tinkering is good, we can learn from Mobx too.

ghost commented 7 years ago

@jbucaran Sorry, all the smaller examples I have found were just too simple (not what you would use MobX for). 😢

This contacts list application which is much simpler than the remote control one.

jorgebucaran commented 7 years ago

@thinkpadder1 Great! The contacts list one looks simple (in scope), so I could probably rewrite it to HyperApp ~in a couple of days~ someday.

Thanks for bearing with me 🙇.

jorgebucaran commented 7 years ago

More examples are coming! In the meantime we should close here since hyperapp will not be adding mobx support anytime soon / ever.