Created
July 9, 2015 16:43
Revisions
-
colinyoung renamed this gist
Jul 9, 2015 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
colinyoung created this gist
Jul 9, 2015 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,192 @@ <!DOCTYPE html> <html> <head> <title>Foo</title> <meta charset='utf-8' /> <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' /> <style type='text/css'> div { cursor:pointer; cursor:hand; position:absolute; top:0; left:0; } </style> <script type='text/javascript'> window.onload = function() { var s = document.getElementsByTagName('div'), cur = 0, ti; if (!s) return; function go(n) { cur = n; var i = 1e3, e = s[n], t; document.body.className = e.dataset.bodyclass || ''; for (var k = 0; k < s.length; k++) s[k].style.display = 'none'; e.style.display = 'inline'; e.style.fontSize = i + 'px'; if (e.firstChild && e.firstChild.nodeName === 'IMG') { document.body.style.backgroundImage = 'url(' + e.firstChild.src + ')'; e.firstChild.style.display = 'none'; if ('classList' in e) e.classList.add('imageText'); } else { document.body.style.backgroundImage = ''; document.body.style.backgroundColor = e.style.backgroundColor; } if (ti !== undefined) window.clearInterval(ti); t = parseInt(e.dataset.timeToNext || 0, 10); if (t > 0) ti = window.setTimeout(fwd, (t * 1000)); while ( e.offsetWidth > window.innerWidth || e.offsetHeight > window.innerHeight) { e.style.fontSize = (i -= 2) + 'px'; if (i < 0) break; } e.style.marginTop = ((window.innerHeight - e.offsetHeight) / 2) + 'px'; if (window.location.hash !== n) window.location.hash = n; document.title = e.textContent || e.innerText; } document.onclick = function() { go(++cur % (s.length)); }; function fwd() { go(Math.min(s.length - 1, ++cur)); } function rev() { go(Math.max(0, --cur)); } document.onkeydown = function(e) { if (e.which === 39 || e.which === 34 || e.which === 40) fwd(); if (e.which === 37 || e.which === 33 || e.which === 38) rev(); }; document.ontouchstart = function(e) { var x0 = e.changedTouches[0].pageX; document.ontouchend = function(e) { var x1 = e.changedTouches[0].pageX; if (x1 - x0 < 0) fwd(); if (x1 - x0 > 0) rev(); }; }; function parse_hash() { return Math.max(Math.min( s.length - 1, parseInt(window.location.hash.substring(1), 10)), 0); } if (window.location.hash) cur = parse_hash() || cur; window.onhashchange = function() { var c = parse_hash(); if (c !== cur) go(c); }; go(cur); }; </script></head><body> <div><h2 id="build-fe-apps-the-mac-way">build FE apps the MAC way</h2> <h4 id="model-action-component-">model, action, component*</h4> <h5 id="-i-just-made-that-up">* i just made that up</h5> </div> <div><p><em>M</em>odels = API</p> </div> <div><p><em>A</em>ctions = Flux</p> </div> <div><p><em>C</em>omponents = React Views</p> </div> <div><p>Why is this different?</p> </div> <div><h2 id="mvc-or-mvvm-">MVC (or MVVM):</h2> <ul> <li>view code in separate files (incl. partials)</li> <li>logic in views, too, at times</li> <li>fat models</li> <li>controllers* tend to contain event code</li> </ul> </div> <div><h2 id="mac-flux-react-">MAC (Flux + React):</h2> <ul> <li>view and model code are in same place (<em>c</em>omponents)</li> <li>events are <em>separate</em>, <em>one-way</em>, and <em>queueable</em></li> <li>server (request-driven) and UI (user-driven) events are in one place</li> </ul> </div> <div><h1 id="actions">Actions</h1> <ul> <li>Dispatched by, shockingly, a dispatcher</li> <li>(usually EventEmitter subclass)</li> </ul> </div> <div><h1 id="components">Components</h1> <ul> <li>View code in <code>render()</code></li> <li>props as <code>this.props</code> (typesafe)</li> <li>view logic in local functions</li> </ul> </div> <div><h1 id="models">Models</h1> <ul> <li>Event driven too!</li> <li>Loading locally? <code>POSTS_LOADED_EVENT</code></li> <li>Loading via JSON request? <code>POSTS_LOADED_EVENT</code></li> </ul> </div> <div><h2 id="for-example-post-component-">For example - Post Component:</h2> <pre><code class="lang-javascript">Post = React.createClass({ render: function() { return <p className="post"></p>; } }); </code></pre> </div> <div><h2 id="blank-post-logic">Blank post logic</h2> <pre><code class="lang-javascript">... body: function() { if (this.text && this.text.length > 0) { return this.text; } else { return "No content in this post."; } }, render: function() { return ( <p className="post"> {this.body()} </p> ); } ... </code></pre> </div> <div><h3 id="one-more-piece-">One more piece!</h3> <h1 id="stores">Stores</h1> </div> <div><h2 id="poststore">PostStore</h2> <pre><code class="lang-javascript">// PostStore.js var _posts = []; var PostStore = assign({}, EventEmitter.prototype, { emitChange: function() { this.emit(CHANGE_EVENT); }, addChangeListener: function(callback) { this.on(CHANGE_EVENT, callback); }, removeChangeListener: function(callback) { this.removeListener(CHANGE_EVENT, callback); } }); ... </code></pre> </div> <div><h2 id="poststore">PostStore</h2> <pre><code class="lang-javascript">// Farther down in PostStore.js var token = PostStore.dispatchToken; token = Dispatcher.register(function(action) { switch (action.type) { // ... case ActionTypes.POST_CHANGED: PostStore.updatePost(action.post); Dispatcher.waitFor([OtherStore.dispatchToken]); PostStore.emitChange(); break; } }); </code></pre> </div> <div><h1 id="in-summary">In Summary</h1> <p>UI/Requests <em>create</em> Actions <em>in</em> Dispatcher, <em>which calls</em> Stores. Stores <em>trigger</em> Components.</p> </div> <div><h1 id="how-to-test">How to Test</h1> <p>Just add synthetic events to your dispatcher. Then, unit test all your Stores, Components, etc.</p> </div>