tutorial reactiva que programacion observables libro ejemplo curso javascript stream rxjs frp rxjs5

javascript - reactiva - rxjs observable



¿Cómo administrar el estado sin utilizar el tema o la manipulación imperativa en un ejemplo simple de RxJS? (1)

Creo que ya encontraste un buen ejemplo con: http://jsbin.com/redeko/edit?js,output .

Te molesta el hecho de que esta implementación

utiliza explícitamente un objeto de estado para agregar y eliminar elementos.

Sin embargo, thas es exactamente la buena práctica que estás buscando. Si cambia el nombre de ese objeto de estado viewModel por ejemplo, podría ser más evidente para usted.

Entonces, ¿qué es el estado?

Habrá otras definiciones, pero me gusta pensar en estado de la siguiente manera:

  • dado f una función impura, es decir, output = f(input) , de modo que puede tener salidas diferentes para la misma entrada, el estado asociado a esa función (cuando existe) es la variable adicional tal que f(input) = output = g(input, state) contiene yg es una función pura.

Entonces, si la función aquí es hacer coincidir un objeto que representa una entrada de usuario, con una matriz de tareas pendientes, y si hago clic en add en una lista de tareas pendientes y ya tengo 2 todos, la salida será de 3 todos. Si hago lo mismo (la misma entrada) en una lista de tareas pendientes con solo un todo, la salida será de 2 todos. Así que la misma entrada, diferentes salidas.

El estado aquí que permite transformar esa función en una función pura es el valor actual de la matriz de tareas pendientes. De modo que mi entrada se convierte en un clic add , Y en la matriz pendiente actual, pasada a través de una función g que da una nueva matriz de tareas pendientes con una nueva lista de tareas pendientes. Esa función g es pura. Entonces f se implementa de una forma apátrida haciendo que su estado previamente oculto sea explícito en g .

Y eso encaja bien con la programación funcional que gira en torno a la composición de funciones puras.

Operadores Rxjs

  • escanear

Entonces, cuando se trata de administración estatal, con RxJS u otra cosa, una buena práctica es hacer que el estado sea explícito para manipularlo.

Si activa la output = g(input, state) en una transmisión, obtiene On+1 = g(In+1, Sn) y eso es exactamente lo que hace el operador de scan .

  • expandir

Otro operador que generaliza el scan se expand , pero hasta ahora tenía muy poco uso de ese operador. scan generalmente hace el truco.

Perdón por la respuesta larga y mathy. Me tomó un tiempo para superar esos conceptos y así los hice comprensibles para mí. Espero que también funcione para ti.

He estado experimentando con RxJS durante dos semanas, y aunque me encanta en principio, parece que no puedo encontrar e implementar el patrón correcto para administrar el estado. Todos los artículos y preguntas parecen estar de acuerdo:

  • Subject debe evitarse siempre que sea posible a favor de solo presionar el estado a través de transformaciones;
  • .getValue() debe estar en desuso por completo; y
  • .do debería quizás evitarse excepto para la manipulación de DOM?

El problema con todas estas sugerencias es que ninguna de las publicaciones parece decir directamente lo que debería estar usando en su lugar, además de "aprenderá el modo Rx y dejará de usar Subject".

Pero no puedo encontrar un ejemplo directo en ninguna parte que indique específicamente la manera correcta de realizar adiciones y eliminaciones a una sola secuencia / objeto, como consecuencia de múltiples otras entradas de flujo, de forma apátrida y funcional.

Antes de volver a señalarme en las mismas direcciones, los problemas con la literatura descubierta son:

  • La introducción a la programación reactiva Te has perdido: excelente texto de inicio, pero no aborda específicamente estas preguntas.
  • El ejemplo TODO para RxJS viene con React e implica la manipulación explícita de Subject s como proxies para React Stores.
  • http://blog.edanschwartz.com/2015/09/18/dead-simple-rxjs-todo-list/ : utiliza explícitamente un objeto de state para agregar y eliminar elementos.

Mi tal vez décima reescritura del estándar TODO sigue - Mis iteraciones anteriores cubiertas incluyen:

  • comenzando con una matriz mutable de "elementos": mala ya que el estado es explícito y está gestionado imperativamente
  • utilizando el scan para concatenar nuevos ítems a un addedItems$ stream, y luego ramificando otro stream donde se eliminaron los ítems eliminados, mal ya que el addedItems$ stream crecería indefinidamente.
  • descubrir BehaviorSubject y usar eso - parecía malo ya que para cada nueva updatedList$.next() , se requiere el valor previo para iterar, lo que significa que Subject.getValue() es esencial.
  • intentar transmitir el resultado de los eventos inputEnter$ addition en los eventos de eliminación filtrados, pero luego cada nueva secuencia crea una nueva lista, y luego alimentar eso en toggleItem$ y toggleAll$ streams significa que cada nueva transmisión depende de la anterior, y por lo que causar una de las 4 acciones (agregar, quitar, alternar elemento o alternar todo) requiere que toda la cadena se ejecute innecesariamente de nuevo.

Ahora he completado el ciclo, donde he vuelto a usar ambos Subject (y ¿cómo se supone que se iterará sucesivamente de ninguna manera sin usar getValue() ?) Y lo do , como se muestra a continuación. Mi colega y yo estamos de acuerdo en que esta es la manera más clara, pero por supuesto parece ser la menos reactiva y la más imperativa. ¡Cualquier sugerencia clara sobre la forma correcta de hacerlo sería muy apreciada!

import Rx from ''rxjs/Rx''; import h from ''virtual-dom/h''; import diff from ''virtual-dom/diff''; import patch from ''virtual-dom/patch''; const todoListContainer = document.querySelector(''#todo-items-container''); const newTodoInput = document.querySelector(''#new-todo''); const todoMain = document.querySelector(''#main''); const todoFooter = document.querySelector(''#footer''); const inputToggleAll = document.querySelector(''#toggle-all''); const ENTER_KEY = 13; // INTENTS const inputEnter$ = Rx.Observable.fromEvent(newTodoInput, ''keyup'') .filter(event => event.keyCode === ENTER_KEY) .map(event => event.target.value) .filter(value => value.trim().length) .map(value => { return { label: value, completed: false }; }); const inputItemClick$ = Rx.Observable.fromEvent(todoListContainer, ''click''); const inputToggleAll$ = Rx.Observable.fromEvent(inputToggleAll, ''click'') .map(event => event.target.checked); const inputToggleItem$ = inputItemClick$ .filter(event => event.target.classList.contains(''toggle'')) .map((event) => { return { label: event.target.nextElementSibling.innerText.trim(), completed: event.target.checked, }; }) const inputDoubleClick$ = Rx.Observable.fromEvent(todoListContainer, ''dblclick'') .filter(event => event.target.tagName === ''LABEL'') .do((event) => { event.target.parentElement.classList.toggle(''editing''); }) .map(event => event.target.innerText.trim()); const inputClickDelete$ = inputItemClick$ .filter(event => event.target.classList.contains(''destroy'')) .map((event) => { return { label: event.target.previousElementSibling.innerText.trim(), completed: false }; }); const list$ = new Rx.BehaviorSubject([]); // MODEL / OPERATIONS const addItem$ = inputEnter$ .do((item) => { inputToggleAll.checked = false; list$.next(list$.getValue().concat(item)); }); const removeItem$ = inputClickDelete$ .do((removeItem) => { list$.next(list$.getValue().filter(item => item.label !== removeItem.label)); }); const toggleAll$ = inputToggleAll$ .do((allComplete) => { list$.next(toggleAllComplete(list$.getValue(), allComplete)); }); function toggleAllComplete(arr, allComplete) { inputToggleAll.checked = allComplete; return arr.map((item) => ({ label: item.label, completed: allComplete })); } const toggleItem$ = inputToggleItem$ .do((toggleItem) => { let allComplete = toggleItem.completed; let noneComplete = !toggleItem.completed; const list = list$.getValue().map(item => { if (item.label === toggleItem.label) { item.completed = toggleItem.completed; } if (allComplete && !item.completed) { allComplete = false; } if (noneComplete && item.completed) { noneComplete = false; } return item; }); if (allComplete) { list$.next(toggleAllComplete(list, true)); return; } if (noneComplete) { list$.next(toggleAllComplete(list, false)); return; } list$.next(list); }); // subscribe to all the events that cause the proxy list$ subject array to be updated Rx.Observable.merge(addItem$, removeItem$, toggleAll$, toggleItem$).subscribe(); list$.subscribe((list) => { // DOM side-effects based on list size todoFooter.style.visibility = todoMain.style.visibility = (list.length) ? ''visible'' : ''hidden''; newTodoInput.value = ''''; }); // RENDERING const tree$ = list$ .map(newList => renderList(newList)); const patches$ = tree$ .bufferCount(2, 1) .map(([oldTree, newTree]) => diff(oldTree, newTree)); const todoList$ = patches$.startWith(document.querySelector(''#todo-list'')) .scan((rootNode, patches) => patch(rootNode, patches)); todoList$.subscribe(); function renderList(arr, allComplete) { return h(''ul#todo-list'', arr.map(val => h(''li'', { className: (val.completed) ? ''completed'' : null, }, [h(''input'', { className: ''toggle'', type: ''checkbox'', checked: val.completed, }), h(''label'', val.label), h(''button'', { className: ''destroy'' }), ]))); }

Editar

En relación con @ user3743222 respuesta muy útil, puedo ver cómo representar el estado como una entrada adicional puede hacer que una función sea pura y así scan es la mejor manera de representar una colección que evoluciona con el tiempo, con una instantánea de su estado anterior hasta ese momento como un parámetro de función adicional.

Sin embargo, así fue como me acerqué a mi segundo intento, con addedItems$ siendo una secuencia escaneada de entradas:

// this list will now grow infinitely, because nothing is ever removed from it at the same time as concatenation? const listWithItemsAdded$ = inputEnter$ .startWith([]) .scan((list, addItem) => list.concat(addItem)); const listWithItemsAddedAndRemoved$ = inputClickDelete$.withLatestFrom(listWithItemsAdded$) .scan((list, removeItem) => list.filter(item => item !== removeItem)); // Now I have to always work from the previous list, to get the incorporated amendments... const listWithItemsAddedAndRemovedAndToggled$ = inputToggleItem$.withLatestFrom(listWithItemsAddedAndRemoved$) .map((item, list) => { if (item.checked === true) { //etc } }) // ... and have the event triggering a bunch of previous inputs it may have nothing to do with. // and so if I have 400 inputs it appears at this stage to still run all the previous functions every time -any- input // changes, even if I just want to change one small part of state const n$ = nminus1$.scan...

La solución obvia sería tener items = [] , y manipularlo directamente, o const items = new BehaviorSubject([]) - pero entonces la única forma de iterar parece ser usando getValue para exponer el estado anterior, que Andre Stalz (CycleJS) ha comentado en los problemas de RxJS como algo que no debería estar realmente expuesto (pero de nuevo, si no, ¿cómo se puede usar?).

Supongo que tuve la idea de que con las transmisiones, se suponía que no debías usar Sujetos o representar algo a través de una ''albóndiga'' estatal, y en la primera respuesta no estoy seguro de cómo esto no introduce corrientes de masas encadenadas que son huérfanos / crecer infinitamente / tienen que construir uno sobre el otro en una secuencia exacta.