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
<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
$$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));
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;
});
¿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!
¿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
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" />
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()
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
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/
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
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
¿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!
¿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
¿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)
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
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
¿Cómo detectar todos los cambios?
• el problema es cuando se ejecuta código fuera de Angular
• Angular2 lo soluciona mediante ‘zonas’
2
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
¿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
¿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
¿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
¿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;
}
}
¿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
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
¿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
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?
Hola, buenos días… gracias por venir a tiempo y elegir estar aquí un sábado por la mañana en lugar de estar haciendo otra cosa.
Me hace mucha ilusión estar aquí, y especialmente abrir el fuego de un día que promete que va a ser muy intenso.
Voy a empezar hablando de Reactividad en los principales frameworks de JavaScript… va a ser una charla muy muy técnica. Yo he aprendido mucho preparándola y espero saber transmitiros las ideas tan interesantes que hay en los tres frameworks.
arrancar demos:
1. event-loop:
cd /Users/jami/devel/charlas/frontfest2018-reactividad/snippets; open event-loop.html
desactivar async stack traces
2. angularjs
cd /Users/jami/devel/charlas/frontfest2018-reactividad/demos/ng-ecommerce; live-server
abrir las dev tools y borrar la consola
3. react
cd /Users/jami/devel/charlas/frontfest2018-reactividad/demos/react-ecommerce; yarn start
4. vue
cd /Users/jami/devel/charlas/frontfest2018-reactividad/demos/vue-ecommerce; yarn dev
OCULTAR BOOKMARKS
CERRAR RESTO PESTAÑAS
DESACTIVAR NOTIFICACIONES
En febrero de 2015 empezamos a desarrollar nuestra plataforma usando AngularJS. Era lo que molaba y nos funcionó estupendamente durante casi dos años. Teníamos la sensación de ser super-productivos y pudimos evolucionar y añadir features con bastante velocidad.
Nuestra plataforma fue creciendo y creciendo y dos años mas tarde, (el año pasado por estas fechas) empezamos a plantearnos que el framework ya no nos estaba funcionando tan bien. Así que empezamos a analizar las tres alternativas obvias: Angular2, React y VueJS
EL PROBLEMA DE LA REACTIVIDAD
cambia un dato -> debe cambiar la UI
cambia un dato -> deben cambiar otros datos
así que esta charla va de eso
de comparar cómo resuelven la reactividad los tres frameworks principales en 2018
a mi me dan mucha rabia las charlas de este tipo
se supone que te van a explicar algo muy difícil, pero luego se quedan solo en lo fácil
y al terminar la charla, te quedas con cara de idiota diciendo… si, pero.. ¿cómo hago el paso 5?
en javascript, los parámetros se pasan por valor
pero se pasan referencias a los objetos o arrays
en javascript, los parámetros se pasan por valor
pero se pasan referencias a los objetos o arrays
hay una cola de tareas
el motor de JavaScript toma la primera tarea de la cola, y la ejecuta HASTA EL FINAL
esa tarea puede encolar nuevas tareas, que se ejecutarán más tarde (asíncronas)
cada evento da lugar a una nueva tarea (handler)
en angular para cada trozo del dom tenemos un scope, que es un objeto que contiene todas las variables y funciones que están disponibles en la plantilla
cada controladora o directiva que usamos crea un scope que va asociado con una parte del DOM
el enlace entre la plantilla y los datos el scope se hace mediante watchers
un watcher es una función que evalúa una expresión y compara el resultado con el resultado anterior, si el resultado es distinto, se llama al callback
un scope tiene una lista de $$watchers: todos los bindings implícitos y explícitos
vamos a verlo
angular.element($0) sobre el padre, ver los $$watchers
angular.element($0) sobre una product-card, ver los $$watchers
cuando AngularJS tiene que detectar cambios, recorre todos los scopes, evaluando todos los $watchers y aplicando los cambios… como en los callback se pueden cambiar las variables, cuando termina, vuelve a repetir el proceso hasta que todos los $watchers se estabilizan
cuando AngularJS tiene que detectar cambios, recorre todos los scopes, evaluando todos los $watchers y aplicando los cambios… como en los callback se pueden cambiar las variables, cuando termina, vuelve a repetir el proceso hasta que todos los $watchers se estabilizan
vamos a verlo
la falta de orientación a componentes (es difícil definir componentes con plantilla + js + estilo)
la gestión del estado
el estado acaba repartido en controladoras y directivas (tiene la estructura del DOM)
el estado de una aplicación mediana o grande no tiene la misma estructura que su presentación en la página
otras críticas que se han hecho a AngularJS = rendimiento y la complejidad del doble-binding
el principal problema de AngularJS es Angular2
Arbol de componentes
Un componente genera HTML a partir de datos crudos (render())
Componentes tienen props y [state] (los datos crudos)
Componentes “tontos” o funcionales (solo props)
Componentes con estado (props + state)
El estado es privado de un componente
Los datos van hacia abajo (a través de las props)
Las modificaciones van hacia arriba (a través de callbacks)
JSX
https://www.facebook.com/notes/facebook-engineering/xhp-a-new-way-to-write-php/294003943919/
reescrito a JavaScript
convertido de servidor a cliente
https://www.facebook.com/notes/facebook-engineering/xhp-a-new-way-to-write-php/294003943919/
reescrito a JavaScript
convertido de servidor a cliente
https://www.facebook.com/notes/facebook-engineering/xhp-a-new-way-to-write-php/294003943919/
https://reactjs.org/blog/2016/09/28/our-first-50000-stars.html
reescrito a JavaScript
convertido de servidor a cliente
React llama al render() de la raíz del árbol de componentes
El componente raíz se renderiza (genera nodos del VirtualDOM) y llama a los componentes hijos para que se rendericen. Los elementos son objetos planos Javascript que dicen la etiqueta, los atributos y los hijos de cada nodo.
React compara el VirtualDOM resultante con el VirtualDOM anterior y extrae las diferencias. Finalmente esas diferencias se aplican al DOM real para actualizar lo que el usuario está viendo
en Vue, las templates son muy parecidas a Angujar.js
se pueden interpolar expresiones de javascript
se pueden asociar valores con los atributos
y se pueden asociar métodos con los eventos
también hay directivas condicionales, y bucles
eso era una buena idea en Angular, y se ha mantenido
donde realmente mejora Vue es en la orientación a componentes
una app vue se escribr a base de ficheros *.vue, uno por cada componente
donde tenemos la template, el código javascript y los estilos TODO en el mismo fichero
donde realmente mejora Vue es en la orientación a componentes
una app vue se escribr a base de ficheros *.vue, uno por cada componente
donde tenemos la template, el código javascript y los estilos TODO en el mismo fichero
cada uno en su lenguaje HTML/Javascript/CSS
y todo en el mismo fichero
para que esto se convierta en algo que pueda cargar un navegador, hace falta que webpack “compile” todos los ficheros .vue y los empaquete en un javascript único y un css único. Hay un plugin de webpack que se llama vue-loader que hace precisamente eso.
Y además, compila las templates a una función render() en javascript (igual que en React)
Si queremos, podemos escribir nosotros la función render() en Javascript. El objetivo es convertir los “datos” en nodos del DOM. La función render genera un VirtualDOM (igual que en react) y luego ese VirtualDOM se compara con el anterior y se aplican los cambios a DOM del navegador.
Incluso, si queremos, podemos escribir las funciones render con JSX… pero… teniendo la posibilidad de escribir el DOM en HTML… ¿para qué querríamos usar JSX? en fin, cada uno elije la droga con la que se mata
La parte final del renderizado de Vue es igual que la de React
Vue llama a la función render de cada componente y monta el VirtualDOM que es un árbol de “elementos”. Los elementos son objetos planos Javascript que dicen la etiqueta, los atributos y los hijos de cada nodo.
Vue compara el VirtualDOM resultante con el VirtualDOM anterior y extrae las diferencias. Finalmente esas diferencias se aplican al DOM real para actualizar lo que el usuario está viendo
demo reactividad
se cachean,
es casi declarativo
let _p1 = 23;const state = { get p1() { console.log('getting p1'); return _p1; }, set p1(value) { console.log('setting p1=', value); _p1 = value;}}
https://vuejs.org/v2/guide/reactivity.html
el año de la convergencia
la reactividad es la diferencia
angularjs abrió el camino
react cambió el enfoque
angular2 se vino arriba
vue combinó lo mejor
no es un tutorial
no es una comparativa para vuer cual elijo
conoce tu framework
y no le pongas las cosas difíciles
los fundamentos
turnos
identidad / mutabilidad / inmutabilidad
mutar o no mutar, esa es la cuestión