Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

Reactividad en Angular, React y VueJS

940 visualizaciones

Publicado el

Slides for my talk in FrontFest 2018 (Madrid, Feb 17). It's a technical comparison of the change detection mechanism as implemented in AngularJS, React, Angular2 and VueJS

See demos at https://github.com/jabadia/frontfest-frameworks-demos

Publicado en: Software
  • Sé el primero en comentar

Reactividad en Angular, React y VueJS

  1. 1. REACTIVIDADen Angular, React yVue @javierabadia
  2. 2. FASHION MEETS BIG DATA 2015
  3. 3. ecosistema comunidad CLI enrutado orientación a componentes rendimiento 💩webpack 💩
  4. 4. FUNDAMENTOS
  5. 5. stack queue heap addToCart() product1 product2 getDiscount() { brand: ‘Salomon’,, name: ‘XMax’,, price: 399.00, } { brand: ‘Atomic’,, name: ‘M110’,, price: 325.00, } Las variables calcPrice(p) p discount newPrice function getDiscount() { return 0.20; } function calcPrice(p) { var discount = getDiscount(); var newPrice = p.price * (1 - discount); p['finalPrice'] = newPrice; } function addToCart() { var product1 = { brand: 'Salomon', name: 'XMax', price: 399.00, }; var product2 = { brand: 'Atomic', name: 'M110', price: 325.00, }; calcPrice(product1); } { brand: ‘Salomon’,, name: ‘XMax’,, price: 399.00, finalPrice 319,20, }0.20 319,20
  6. 6. stack queue heap { brand: ‘Atomic’,, name: ‘M110’,, price: 325.00, } Las variables function getDiscount() { return 0.20; } function calcPrice(p) { var discount = getDiscount(); var newPrice = p.price * (1 - discount); p['finalPrice'] = newPrice; } function addToCart() { var product1 = { brand: 'Salomon', name: 'XMax', price: 399.00, }; var product2 = { brand: 'Atomic', name: 'M110', price: 325.00, }; calcPrice(product1); } { brand: ‘Salomon’,, name: ‘XMax’,, price: 399.00, finalPrice 319,20, }
  7. 7. stack queue heap El bucle de eventos o “los turnos” task1 task2 (onclick) task3 (xhr) fn1.1() fn1.2() fn1.3() fn2.1() fn2.2()
  8. 8. todo junto stack queue heap
  9. 9. ¿Pero esto puede funcionar? AngularJS
  10. 10. scope (MainCtrl) { additionalDiscount: 0, products: [ {name: 'XMax X10 2018’, … }, {name: 'Magnum’, …}, {name: 'Amphibio XII’, …}, {name: 'Vector Edge’, …}, {name: 'Fogbreaker’, …}, ], addToCart: function (product) { alert(`added ${product.name}`); }, } scope (ProductCard) { product: {name: 'XMax X10 2018’, … }, additionalDiscount: 0, addToCart: fn(), isDiscounted: fn(), }, AngularJS en una slide: scopes
  11. 11. <div ng-controller="MainCtrl"> counter: {{counter}} doubleCounter: {{doubleCounter}} <button ng-click="increment()">increment</button> </div> app.controller('MainCtrl', function ($scope) { $scope.counter = 0; $scope.increment = function () { $scope.counter += 1; }; $scope.$watch('counter', function () { $scope.doubleCounter = $scope.counter * 2; }); }); $scope = { counter: 0, doubleCounter: 0, ... } AngularJS en una slide: watchers bindings en la plantilla watchers explícitos
  12. 12. $$watchers do { // "traverse the scopes" loop if ((watchers = !current.$$suspended && current.$$watchers)) { // process our watches watchers.$$digestWatchIndex = watchers.length; while (watchers.$$digestWatchIndex--) { try { watch = watchers[watchers.$$digestWatchIndex]; // Most common watches are on primitives, in which case we can short // circuit it with === operator, only when === fails do we use .equals if (watch) { get = watch.get; if ((value = get(current)) !== (last = watch.last) && !(watch.eq ? equals(value, last) : (isNumberNaN(value) && isNumberNaN(last)))) { dirty = true; lastDirtyWatch = watch; watch.last = watch.eq ? copy(value, null) : value; fn = watch.fn; fn(value, ((last === initWatchVal) ? value : last), current); if (ttl < 5) { logIdx = 4 - ttl; if (!watchLog[logIdx]) watchLog[logIdx] = []; watchLog[logIdx].push({ msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, newVal: value, oldVal: last }); } } else if (watch === lastDirtyWatch) { // If the most recently dirty watcher is now clean, short circuit since the remaining watchers // have already been tested. dirty = false; break traverseScopesLoop; } } } catch (e) { $exceptionHandler(e); } } } // Insanity Warning: scope depth-first traversal // yes, this code is a bit crazy, but it works and we have tests to prove it! // this piece should be kept in sync with the traversal in $broadcast // (though it differs due to having the extra check for $$suspended and does not // check $$listenerCount) if (!(next = ((!current.$$suspended && current.$$watchersCount && current.$$childHead) || (current !== target && current.$$nextSibling)))) { while (current !== target && !(next = current.$$nextSibling)) { current = current.$parent; } } } while ((current = next));
  13. 13. dirty checking = ciclo de $digest / $apply • para cada $scope • para cada watcher en $$watchers • evalúa la expresión (p.ej. ‘counter’) • compara con el resultado anterior (p.ej 3 === 2) • si es distinto, ejecuta el callback • en caso de un binding de la plantilla, el callback cambia el valor en el DOM • como los callbacks pueden cambiar cosas, se repite el ciclo hasta que no haya ningún cambio scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { node[0].nodeValue = value; });
  14. 14. ¿cuándo? • al final de cada turno • siempre y cuando lo haya iniciado una tarea de Angular.js • por ejemplo • ng-click • $timeout, $interval • $http stack queue heap baz() foo() product1 product2 bar() p discount newPrice ng-click() ahora!
  15. 15. ¿y si ocurre algo fuera? • AngularJS no se entera • tenemos que llamar a $scope.$apply() manualmente • p.ej. • usando d3.js • eventos nativos de librerías externas • eventos que podemos recibir de otro iframe
  16. 16. el doble binding • ng-model = mola • se vinieron arriba • en las directivas propias • ufff <the-counter counter='counter'></the-counter> app.directive('theCounter', function () { return { scope: { counter: '=', }, template: ` <div> {{counter}} <button ng-click="dec()">dec</button> </div>`, link(scope, el, attr) { scope.dec = function() { scope.counter -= 1; }; }, }; }); <input type="text" ng-model="passportId" />
  17. 17. Cómo ayudar a AngularJS • usar pocos watchers • y si es posible, que cambien pocas cosas? • no poner muchos bindings • usar bind once (p.ej {{::brand}}) • ng-repeat track by • virtualizar listas • $watch / $watch(…,…,true) / $watchGroup() / $watchCollection()
  18. 18. Los problemas de AngularJS • la falta de orientación a componentes • herencia de scopes • la falta de un mecanismo de gestión de estado • no tan importantes (EMHO) • rendimiento con muchos bindings/watchers • complejidad derivada del doble binding • Angular2
  19. 19. React un pasado oscuro…
  20. 20. React en una slide <App /> <ProductCard /> <DiscountTag /><Price /> <ProductCard /> <DiscountTag /><Price /> render() props state elements render()
  21. 21. El origen de React es… • PHP
  22. 22. El origen de React es… • PHP PHP? React?
  23. 23. El origen de React es… • PHP / XHP • reescrito a JavaScript • convertido de servidor a cliente $list = <ul />; foreach ($items as $item) { $list->appendChild(<li>{$item}</li>); } https://www.facebook.com/notes/facebook-engineering/xhp-a- new-way-to-write-php/294003943919/
  24. 24. Como los videojuegos • cambio el estado • renderizo todo • DOM LEEEEENTOOOOO • no tiene por qué • si se hace mal • reflow & Paint • ¡DOM guarda estado! • selección • foco • scroll
  25. 25. por eso - VirtualDOM { type: 'button', props: { className: 'button button-blue', children: { type: 'b', props: { children: 'OK!' } } } } <button class="button button-blue"> <b>OK!</b> </button> objetos que representan el DOM equivale a
  26. 26. VirtualDOMComponentes “parchear” el DOM real VirtualDOM anterior render() diff()
  27. 27. ¿cuándo hay que renderizar? • tenemos que avisar a REACT • this.setState({…}) • encola el componente (y sus hijos) para renderizar • ¿cuándo se renderiza? • batched updates - a veces!
  28. 28. ¿y la reactividad dato → dato? • Fuera del alcance de React • O dicho de otra forma, ¿comunicación entre componentes que están lejos? • PubSub • Eventos • Redux • Mobx • Redux-saga
  29. 29. ¿Cómo ayudar a React? • no todo debe ir en this.state • se pueden añadir propiedades al componente fuera de la ‘jurisdicción’ de React • shouldUpdateComponent() • datos inmutables • virtualizar listas • Perf.printWasted() (<16, con Fiber → browserTimeline)
  30. 30. Angular 2+ Para los que echan de menos Java y C#
  31. 31. Detección de Cambios en Angular2 • sigue usando dirty-checking • igual que AngularJS • solucionar los problemas de AngularJS • las múltiples vueltas de $digest/$apply • los cambios fuera de Angular • además se les fue la pinza conTypeScript y otras 1000 cosas 1 2
  32. 32. Cómo evitar las multiples vueltas • se eliminan los watchers de usuario • por lo tanto, no puedo cambiar el estado en el ciclo de comprobación 1
  33. 33. ¿Cómo detectar todos los cambios? • el problema es cuando se ejecuta código fuera de Angular • Angular2 lo soluciona mediante ‘zonas’ 2
  34. 34. function doSomething() { console.log('Async task'); } function foo() { bar(); setTimeout(doSomething, 2000); baz(); } foo();
  35. 35. myZoneSpec = { beforeTask: () => { console.log('entering zone'); }, afterTask: () => { console.log('leaving zone'); }, }; function doSomething() { console.log('Async task'); } function foo() { bar(); setTimeout(doSomething, 2000); baz(); } let myZone = zone.fork(myZoneSpec); myZone.run(foo); zones.js
  36. 36. zones.js • ¿Es mágia? • No… es monkey-patching de los event listener, setTimeout(), setInterval(), xhr • Por eso ya no es necesario usar $timeout o $interval
  37. 37. ¿Cómo detectar todos los cambios? • Zonas • zones.js permite detectar todas las tareas que ‘nacen’ de una misma tarea (eso es una zona) • p.ej. permite hacer call-stacks de llamadas asíncronas • p.ej. permite registrar eventos al entrar o salir de una zona (empezar a ejecutar una tarea o finalizarla) • Se “envuelve” todo el código de Angular en una zona • detecta cambios cuando la cola de tareas de la zona de Angular está vacía 2
  38. 38. Reactividad dato → dato = RxJS • Functional Reactie Programming • Observables • pipes • streams // config.service.ts getConfig() { return this.http.get('assets/config.json'); } // in component.ts showConfig() { this.configService.getConfig() .subscribe(data => this.config = { heroesUrl: data['heroesUrl'], textfile: data['textfile'] }); }
  39. 39. ¿Cómo ayudar a Angular? • ChangeDetectionStrategy.OnPush • Track By • RxJS • Pure Pipes
  40. 40. Vue.js el front vuelve a ser divertido
  41. 41. Vue.js en una slide <template> <div class="product-card"> <div class="description"> <div class="name">{{product.name}}</div> <div class="brand">{{product.brand}}</div> <div class="price--original" v-if="isDiscounted"> <price :price="product.price"/> </div> <div class="price"> <price :price='finalPrice'/> </div> <button class="nice-button" @click="addToCart(product)">Add to Cart</button> </div> <div class="img"> <discount-tag class="discount" :discount="product.discount"/> <img v-if="product.img" :src="product.img" :alt="product.name"> </div> </div> </template> sintaxis de las templates parecida a Angular.js bindings de atributos bindings de eventos
  42. 42. Ficheros *.vue <script> </script> <style> </style> <template> </template>
  43. 43. Ficheros *.vue <script> </script> <style> </style> <template> </template>
  44. 44. Ficheros *.vue javascript template estilos <template> <div class="product-card"> </div> </template> <script> import Price from './Price'; import DiscountTag from './DiscountTag'; export default { props: { product: Object, overallDiscount: Number, }, computed: { … finalPrice() { return this.product.price * (1 - this.effectiveDiscount); }, … }, methods: { addToCart(product) { alert(`added ${product.name}`); } }, components: { Price, DiscountTag, }, }; </script> <style lang="less" rel="stylesheet/less"> … </style> las templates se “compilan” a una función render()
  45. 45. Función render() <template> <div class="discount-tag" v-if="discount !== 0">{{discount * 100}}%</div> </template> <script> export default { props: { discount: Number, }, }; </script> <script> export default { functional: true, render: function (h, context) { const discount = context.props.discount; return discount ? h( 'div', {'class': 'discount-tag ' + context.data.staticClass}, (context.props.discount * 100) + '%', ) : undefined; }, props: { discount: Number, }, }; </script> <script> export default { functional: true, render: function (h, context) { const discount = context.props.discount; return discount ? ( <div class={'discount-tag'} {...context.data}> {(context.props.discount * 100) + '%'} </div> ) : undefined; }, props: { discount: Number, }, }; </script> VirtualDOM es lo mismo
  46. 46. VirtualDOMComponentes “parchear” el DOM real VirtualDOM anterior render() diff()
  47. 47. ¿cuándo se llama a render() de cada componente?
  48. 48. ¿como sabe cuándo hay que renderizar? • Angular.js - dirty-checking • al final de cada turno, evalúa todos los watchers y actualiza los nodos correspondientes • React.js - como los videojuegos • cuando se llama a setState() de un componente • re-renderiza todos sus hijos • Vue.js • cuando cambia un dato “reactivo” • re-renderiza los componentes que dependen de el dato
  49. 49. ¿que datos son reactivos? • componente • data (= estado) • props • propiedades computadas Vue.js cuando cambia un dato “reactivo” re-renderiza los componentes que dependen de el dato
  50. 50. Propiedades Computadas computed: { finalPrice() { return this.product.price * (1 - this.effectiveDiscount); }, effectiveDiscount() { return this.product.discount + this.overallDiscount; }, }, <template> <div class="product-card"> <span>{{finalPrice}}</span> </div> </template>
  51. 51. ¿cómo sabe que cambia? • Vue.js reemplaza los “datos” por propiedades ES5 con getter y setter Object.defineProperty() Vue.js cuando cambia un dato “reactivo” re-renderiza los componentes que dependen de el dato let _p1 = 23; const state = { get p1() { console.log('getting p1’); return _p1; }, set p1(value) { console.log('setting p1=', value); _p1 = value; } }
  52. 52. ¿cómo sabe qué componentes dependen del dato? Vue.js cuando cambia un dato “reactivo” re-renderiza los componentes que dependen de el dato https://vuejs.org/v2/guide/reactivity.html
  53. 53. getters y setters en vue.js • los setters • cambian el dato • avisa a quien dependa de mi de que el dato ha cambiado • los getters • devuelven el dato • actualizan la lista de dependencias para poder avisar
  54. 54. ¿Cómo ayudar a Vue.js? • no necesita mucha ayuda • mutar objetos, en lugar de reemplazarlos • uso ‘extensivo’ de computed properties • tener en cuenta los “change detection caveats” https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
  55. 55. RESUMIENDO
  56. 56. Y todo esto… para que sirve? • un factor más para elegir el framework que más se adapta a nuestro estilo/proyecto/gusto • para usar los frameworks mejor • go with the flow • evitar ‘trampas’ • optimizar rendimiento • para migrar de un framework a otro sobre la marcha • ¿una app donde conviven dos (o más) frameworks?
  57. 57. GRACIAS
  58. 58. GRACIAS@javierabadia
  59. 59. Referencias • MDN Event Loop: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop • EvanYou, Reactivity in Frontend JavaScript Frameworks https://www.dotconferences.com/2016/12/evan-you-reactivity-in-frontend-javascript-frameworks • Change Detection in JS Frameworks: http://teropa.info/blog/2015/03/02/change-and-its-detection-in- javascript-frameworks.html
  60. 60. Referencias - Angular • Improve Angular Performance: https://www.alexkras.com/11-tips-to-improve-angularjs-performance/#watchers • Angular Code Overview: https://angular.io/guide/architecture • Change Detection in Angular 2: https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c, https://codewithstyle.info/change-detection-angular-versus-angularjs/, https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html • Zones: https://blog.thoughtram.io/angular/2016/01/22/understanding-zones.html, https://www.joshmorony.com/understanding-zones-and-change-detection-in-ionic-2-angular-2/, https://angular-2-training- book.rangle.io/handout/zones/ • Zone Primer: https://docs.google.com/document/d/1F5Ug0jcrm031vhSMJEOgp1l-Is-Vf0UCNDY- LsQtAIY/edit#heading=h.s6p06rpu4c13 • RxJS: https://auth0.com/blog/making-use-of-rxjs-angular/, https://medium.com/google-developer-experts/angular- introduction-to-reactive-extensions-rxjs-a86a7430a61f, https://blog.angular-university.io/functional-reactive-programming- for-angular-2-developers-rxjs-and-observables/, https://stackoverflow.com/questions/37364973/angular-promise-vs- observable, https://alligator.io/vuejs/using-rxjs/ • Angular2 performance: https://netbasal.com/optimizing-the-performance-of-your-angular-application-f222f1c16354 • Observables: https://angular-2-training-book.rangle.io/handout/observables/ • State management: https://angular-2-training-book.rangle.io/handout/state-management/
  61. 61. Referencias - React • XHP: https://www.facebook.com/notes/facebook-engineering/xhp-a-new-way-to-write-php/294003943919/ • From XHP to React: https://reactjs.org/blog/2016/09/28/our-first-50000-stars.html • History of React: https://thenewstack.io/javascripts-history-and-how-it-led-to-reactjs/ • React Codebase Overview: https://reactjs.org/docs/codebase-overview.html • VirtualDOM and internals: https://reactjs.org/docs/faq-internals.html, https://reactjs.org/docs/rendering- elements.html, https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html • Cristopher Chedeau (Facebook), React’s Architecture https://code.facebook.com/videos/1507312032855537/oscon-2014-react-s-architecture/ • How to communicate between ReactJS components: https://www.ctheu.com/2015/02/12/how-to-communicate- between-react-components/, https://anthonymineo.com/communication-between-independent-components- in-react-using-pubsubjs/ • React Props vs State: https://github.com/uberVU/react-guide/blob/master/props-vs-state.md, http://lucybain.com/blog/2016/react-state-vs-pros/ • React State and Lifecycle: https://reactjs.org/docs/state-and-lifecycle.html • Optimizing React: https://reactjs.org/docs/optimizing-performance.html, https://marmelab.com/blog/2017/02/06/react-is-slow-react-is-fast.html • React + Redux: https://stackoverflow.com/questions/40386128/how-does-a-redux-connected-component- know-when-to-re-render
  62. 62. Referencias - VueJS • Vue Code Overview: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md • Vue a better React: https://mattcampbell.net/slides/vue/#0 • Vue.js Reactivity: https://vuejs.org/v2/guide/reactivity.html, https://vuejsdevelopers.com/2017/03/05/vue-js-reactivity/ • Change Detection Caveats: https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats

×