Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Effective Application State Management (@DevCamp2017)

36 views

Published on

These are the slides of my talk at DevCamp 2017 (http://www.devcamp.com.br), Campinas, Brazil.

It's about best practices for better application state management. It's mainly derived from my many years experiences with React, Flux, Redux, etc. and other front-end technologies, and shall give some tips on how to deal with states in component based web applications.

Originally, it's an interactive slideshow (made with ReactJS ;-) ) and better be viewed here: https://ohager.github.io/devcamp2017_slides

Hint: you may use Google's online translation for more or less comprehensive translations to other languages

Published in: Software
  • Be the first to comment

  • Be the first to like this

Effective Application State Management (@DevCamp2017)

  1. 1. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 1/55 EFFECTIVE APPLICATION STATE MANAGEMENT Oliver Hager - Dextra 🤘
  2. 2. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 2/55 O que é o estado de aplicação?
  3. 3. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 3/55
  4. 4. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 4/55
  5. 5. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 5/55 LIÇÃO 1
  6. 6. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 6/55 BAD 💩 render(){ return ( this.state.userRole === 'admin' ? <AdminComponent/> : <UserComponent/> ) }
  7. 7. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 7/55 GOOD 👍 render(){ return ( this.state.userIsAdmin ? <AdminComponent/> : <UserComponent/> ) }
  8. 8. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 8/55 render(){ function calculateTotalCost(items){ return items.reduce( (p,item) => p+(item.price*item.quantity), 0.0 ).toFixed(2); } return ( <div> <CartItemList items={this.state.cartItems}/> <div className='cart-total'>{ calculateTotalCost(this.state.cartItems) + '$' }<div/> <div/> ) }
  9. 9. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 9/55 ⭐ Mantenha mais simples possível, p.e. booleano
  10. 10. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 10/55 LIÇÃO 2 Compose your state
  11. 11. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 11/55 TRIVIAL 👍 const update = (state, newState) => Object.assign({}, state, newState); const updateState = (state, newState) => ({ ...state, ...newState }); //
  12. 12. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 12/55 ASSIGN/... VS MERGE New Repl ohagermy repls community BETA DevCamp2017: Assign vs Merge share save run Babel Compiler v6.4.4 Copyright (c) 2014-2015 Sebastian McKenzie
  13. 13. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 13/55 ⭐ Use composição para montar estado
  14. 14. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 14/55 LIÇÃO 3 Make your state immutable
  15. 15. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 15/55
  16. 16. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 16/55 class ItemList extends Component { constructor() { this.state.items = Store.getState().items } // assume this method is called when our store was updated onStoreUpdate() { this.setState({items: Store.getState().items }) } addItem(item) { let items = this.state.items items.push(item) // Bad 💩 } render() { return ( <ItemEditor onAdd = {this.addItem} /> /*...*/ ) } }
  17. 17. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 17/55 class ItemList extends Component { constructor() { this.state.items = Store.getState().items } // assume this method is called when our store was updated onStoreUpdate() { this.setState({items: Store.getState().items }) } addItem(item) { let items = this.state.items items.push(item) // State mutated in STORE! } render() { return ( <ItemEditor onAdd = {this.addItem} /> /*...*/ ) } }
  18. 18. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 18/55 SOLUÇÃO SUBÓTIMA 😕 addItem(item) { let items = _.cloneDeep(this.state.items) items.push(item) // store won't be mutated Store.update({items: items}) }
  19. 19. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 19/55 SOLUÇÃO MELHOR 😊 constructor() { // Copy by reference for immutable data is ok! this.state.items = Store.getImmutableState().get('items') } // ... addItem(item) { // you cannot simply add an item to the list, you need to use the API let copiedItems = Immutable(this.state.items).asMutable() copiedItems.push(item) // assumes that update() makes the state somehow immutable Store.update({items: copiedItems}) }
  20. 20. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 20/55 COMO FAZER IMUTÁVEL? 🤔 New Repl ohagermy repls community BETA DevCamp2017: Immutable share save run Babel Compiler v6.4.4 Copyright (c) 2014-2015 Sebastian McKenzie
  21. 21. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 21/55 COMO FAZER IMUTÁVEL? 🤔 https://github.com/facebook/immutable-js [60KiB] https://github.com/rtfeldman/seamless-immutable [8KiB] https://github.com/Yomguithereal/baobab [33KiB] https://github.com/planttheidea/crio [51KiB] ... etc etc pp
  22. 22. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 22/55 THE GOOD THINGS 🍺 // optimize our rendering process (React-wise) shouldComponentUpdate(nextProps, nextState){ // comparing by reference return this.state.items !== nextState.items; } ⭐ Evite alterações acidentais (bom para times)
  23. 23. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 23/55 THE BAD THING(S) 🍋 ⭐ Mais verboso/trabalhoso para alterar
  24. 24. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 24/55 LIÇÃO 4 Flatten the state Keep it simple 2
  25. 25. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 25/55 const posts = [ { "id": 123, "author": { "id": 1, "name": "Paul" }, "title": "My awesome blog post", "comments": [ { "id": 324, "commenter": { "id": 2, "name": "Nicole", "text" : "Nicole + Paul = Love 💕" } }, { "id": 325, "commenter": { "id": 5, "name": "Klaus", "text" : "Yo, Mann. Geiler Scheiss!" } } ] } ]; PROBLEM👾
  26. 26. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 26/55 UPDATE posts.find( p => p.id === 123).author.name = 'Dude'; UPDATE IMMUTABLE // update const post = posts.find( p => p.id === 123); // seamless-immutable API const updatedPost = Immutable.setIn(post, ["author", "name"], "Elmar"); // insert const atIndex = posts.findIndex( p => p.id === 123); const newPost = { /* ... */ }; posts.slice(0, atIndex).concat(newPost).concat(posts.slice(atIndex+1)); // 😲 WTF?
  27. 27. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 27/55 const normalizedPosts = { "posts": [ { "id": 123, "authorId": 1, "title": "My awesome blog post", "comments": [324, 325] } // ... more posts ], "comments": [ { "id": 324, "authorId": 2 }, { "id": 325, "authorId": 5 } ], "authors": [ { "id": 1, "name": "Paul" }, { "id": 2, "name": "Nicole" }, { FLATTENED/NORMALIZED
  28. 28. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 28/55 ⭐ "Estados rasos" simpli cam acesso/alterações
  29. 29. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 29/55 LIÇÃO 5 Determine your state's scope
  30. 30. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 30/55 STORY TIME
  31. 31. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 31/55 ONCE UPON A TIME ⚫ Quis criar um suggestion/autocomplete
  32. 32. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 32/55 SO SAD
  33. 33. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 33/55 ACTION CREATOR // Action Creator - ES5 old school style, babe function fetchSuggestions(componentId, url, searchTerm){ var suggestionService = new GenericService(url); suggestionService.getAll({ textFilter: searchTerm, //... }) .then(function (suggestions) { this.dispatch('fetchSuggestions', { componentId : componentId, // Hu, I need to manage the component instance! suggestions : suggestions }); }.bind(this)); }
  34. 34. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 34/55 SUGGESTION STORE var _suggestions = {}; var suggestionStore = { // get the suggestions for specific component getSuggestions : function(componentId){ // WTF? return _suggestions[componentId]; // WTF? }, // called on dispatch('fetchSuggestions',...) // -- nanoflux uses a convention based auto-mapping onFetchSuggestions : function(data){ _suggestions[data.componentId] = Immutable(data.suggestions); // WTF? this.notifyListeners(); } };
  35. 35. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 35/55 ⭐ A LISTA DE SUGESTÕES NÃO É ESTADO DA APLICAÇÃO
  36. 36. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 36/55 ⭐ A LISTA DE SUGESTÕES É ESTADO DO COMPONENTE!
  37. 37. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 37/55 COMO DETERMINAR O ESCOPO DO ESTADO? Componente Aplicação
  38. 38. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 38/55 LIÇÃO 6 Manage your state globally
  39. 39. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 39/55
  40. 40. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 40/55 BASIC PRINCIPLES ⭐ Deixar acessível por todos os componentes ⭐ Alterar o estado apenas em um único ponto bem de nido
  41. 41. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 41/55 ACESSO AO ESTADO Reactive - Hollywood principle: Don't call us, we call you! 📞
  42. 42. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 42/55 OBSERVER/PUBSUB function notify(listeners, state) { for(const pn in listeners){ const l = listeners[pn] l.f.call(l.c, state) } } class ApplicationStateManager { constructor(){ this._listenerCount = 0; this._listeners = {}; this._state = {}; } listen(fn, ctx) { // use object map instead of array (is smaller and faster) this._listeners[++this._listenerCount] = { f: fn, c: ctx } return this._listenerCount } unlisten(listenerId) { delete this._listeners[listenerId]; } update(args){ //... update stuff notify(this._listeners, this._state) } getState() { return this._state } }
  43. 43. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 43/55 USING BROWSER EVENTS class ApplicationStateManager { constructor(){ this._state = {}; } update(args){ //... update stuff window.dispatchEvent(new CustomEvent('stateUpdate', { 'detail': this._state }) ) } getState() { return this._state } } // Usage const mgr = new ApplicationStateManager(); window.addEventListener('stateUpdate', e => { console.log('state updated', e.detail)} ) mgr.update( {} ) // fake update here! window.removeEventListener('stateUpdate');
  44. 44. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 44/55 ALTERAR O ESTADO "Single Source of Truth"🍺
  45. 45. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 45/55 function deepFreeze(obj) { for(let pn in obj) { if (typeof obj[pn] === 'object') deepFreeze(obj[pn]) } return Object.freeze(obj) } class ApplicationStateManager { constructor(){ // ... this._state = {}; } /* ... */ update(fn){ let s = this._state; s = deepFreeze(Object.assign({}, s, fn(s))) notify(this._listeners, s) } getState() { return this._state } // immutable, YAY! } UPDATE
  46. 46. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 46/55 UPDATE USAGE function addItem(newItem){ appStateManager.update( state => { // need to copy the state, as it is immutable let items = _.cloneDeep(state.items || []); items.push(newItem); return {items: items}; // return new state slice }) }
  47. 47. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 47/55 BASIC PRINCIPLES RECAP ⭐ Acesso ao estado de forma reativa com Observer/PubSub ou similar ⭐ Estado alterável apenas pelo próprio State Manager (imutabilidade força)
  48. 48. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 48/55 LIÇÃO 7 Break huge states in parts
  49. 49. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 49/55 BEST PRACTICES OF THE TOP DOGS https://facebook.github.io/ ux/docs/ ux-utils.html#store http://redux.js.org/docs/recipes/reducers/UsingCombineReducers.htm https://mobx.js.org/best/store.html ⭐ Flux suporta múltiplos "Stores" ⭐ Redux promove vários "Reducers" ⭐ MobX recomenda "Domain Stores"
  50. 50. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 50/55 SEPARAÇÃO POR CONTEXTO - APLICAÇÃO Noti cações globais, Modais, Indicadores de Carregamento, etc.
  51. 51. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 51/55 SEPARAÇÃO POR CONTEXTO - DOMÍNIO Pedidos, Serviços, Produtos, Compras, Vendas etc.
  52. 52. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 52/55 RESUMO
  53. 53. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 53/55 JUST FOR FUN! Stappo Demo This small demo is built with RiotJS, BlazeCSS and Webpack Clear All Add Your items Stappo Stats npmnpm v0.0.7v0.0.7 Build Type Size [bytes] Generic Bundle 1271 Web Bundle 1243 Generic 337 Web 274 Enter name of item to add... No Items Here What are you searching for?
  54. 54. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 54/55 274 BYTES! function Stappo(a){function d(b){for(var a in b)"object"==typeof b[a]&&d(b[a]);return Object.freeze(b)}a=void 0===a?"stappo":a;var c= {};this.update=function(b) {c=d(Object.assign({},c,b()));window.dispatchEvent(new CustomEvent(a,{detail:c}))};this.get=function(){return c}}; Fully reactive App State Manager with immutable states https://github.com/ohager/stappo
  55. 55. 6/29/2017 DevCamp 2017 https://ohager.github.io/devcamp2017_slides/#/?export& 55/55 DANKE!🤘 github.com/ohager linkedin.com/in/oliverhager devbutze.com

×