Mrr: FRP total para reaccionar

Mrr es una biblioteca funcional-reactiva para React (pido disculpas por la tautología imaginaria).

La palabra "reactividad" generalmente se refiere a Rx.js como una referencia FRP. Sin embargo, una serie de artículos recientes sobre este tema en Habré ( [1] , [2] , [3] ) mostraron la complejidad de las soluciones en Rx, que en ejemplos simples perdieron en claridad y simplicidad a casi cualquier otro enfoque. Rx es grande y poderoso, y es perfecto para resolver problemas en los que se sugiere la abstracción del flujo (en la práctica, esto es principalmente la coordinación de tareas asincrónicas). ¿Pero escribiría, por ejemplo, una validación de forma síncrona simple en Rx? ¿Le ahorraría tiempo en comparación con los enfoques imperativos habituales?

mrr es un intento de probar que FRF puede ser una solución conveniente y efectiva no solo en problemas específicos de "transmisión", sino también en las tareas de front-end de rutina más comunes.

La programación reactiva es una abstracción muy poderosa, en este momento en la interfaz está presente de dos maneras:

  • variables reactivas (variables calculadas): simples, confiables, intuitivas, pero el potencial del RP está lejos de ser completamente revelado
  • bibliotecas para trabajar con flujos, como Rx, Bacon, etc .: potente, pero bastante complicado, el alcance del uso práctico se limita a tareas específicas.

Mrr combina las ventajas de estos enfoques. A diferencia de Rx.js, mrr tiene una API corta, que el usuario puede ampliar con sus adiciones. En lugar de docenas de métodos y operadores: cuatro operadores básicos, en lugar de Observable (caliente y frío), Asunto, etc. - una abstracción: corriente. Además, mrr carece de algunos conceptos complejos que pueden complicar significativamente la legibilidad del código, por ejemplo, metastreams.

Sin embargo, mrr no es un "Rx simplificado de una nueva manera". Basado en los mismos principios básicos que Rx, mrr afirma ser un nicho más grande: administrar el estado global y local (a nivel de componente) de la aplicación. Aunque el concepto original de programación reactiva estaba destinado a trabajar con tareas asincrónicas, mrr ha utilizado con éxito enfoques de reactividad para tareas sincrónicas ordinarias. Este es el principio de "FRP total".

A menudo, al crear una aplicación en React, se utilizan varias tecnologías diferentes: recomponer (o pronto - ganchos) para el estado del componente, Redux / mobx para el estado global, Rx con redux-observable (o thunk / saga) para gestionar los efectos secundarios y coordinar asincrónico tareas en el editor. En lugar de tal "ensalada" de diferentes enfoques y tecnologías dentro de la misma aplicación, con mrr puede usar una sola tecnología y paradigma.

La interfaz mrr también es significativamente diferente de Rx y bibliotecas similares: es más declarativa. Gracias a la abstracción de reactividad y al enfoque declarativo, mrr le permite escribir código expresivo y conciso. Por ejemplo, el estándar TodoMVC en mrr toma menos de 50 líneas de código (sin contar la plantilla JSX).

Pero suficiente publicidad. ¿Se las arregló para combinar las ventajas del RP "ligero" y "pesado" en una botella? Debe juzgar, pero primero, lea los ejemplos de código.

TodoMVC ya está bastante dolorido, y el ejemplo de descargar datos sobre usuarios de Github es demasiado primitivo para poder sentir las características de la biblioteca en él. Consideraremos a mrr como un ejemplo de una aplicación condicional para comprar boletos de tren. En nuestra interfaz de usuario habrá campos para elegir las estaciones de inicio y finalización, fechas. Luego, después de enviar los datos, se devolverá una lista de trenes disponibles y lugares en ellos. Después de seleccionar un tren y un tipo de automóvil específicos, el usuario ingresará los datos del pasajero y luego agregará los boletos a la cesta. Vamos

Necesitamos un formulario con una selección de estaciones y fechas:



Crear campos de autocompletado para ingresar estaciones.

import { withMrr } from 'mrr'; const stations = [ '', '', '', ' ', ... ] const Tickets = withMrr({ //    $init: { stationFromOptions: [], stationFromInput: '', }, //   - "" stationFromOptions: [str => stations.filter(s => s.indexOf(str)===0), 'stationFromInput'], }, (state, props, $) => { return (<div> <h3>    </h3> <div>  : <input onChange={ $('stationFromInput') } /> </div> <ul className="stationFromOptions"> { state.stationFromOptions.map(s => <li>{ s }</li>) } </ul> </div>); }); export default Tickets; 

Los componentes MRR se crean utilizando la función withMrr, que acepta un diagrama de enlace reactivo (descripción de flujos) y una función de representación. Las funciones de procesamiento se pasan a los accesorios del componente, así como al estado, que ahora está completamente controlado por mrr. Contendrá el inicial (bloque $ init) y se calculará por los valores de la fórmula de las celdas reactivas.

Ahora tenemos dos celdas (o dos flujos, que es lo mismo): stationFromInput , los valores a los que recaen desde la entrada del usuario usando $ helper (pasando event.target.value por defecto para los elementos de entrada de datos), y una celda derivada de él stationFromOptions , que contiene una matriz de estaciones coincidentes por nombre.

El valor de stationFromOptions se calcula automáticamente cada vez que se cambia una celda principal utilizando una función (en la terminología de MRR llamada " fórmula ", por analogía con las fórmulas de Excel). La sintaxis de las expresiones mrr es simple: en primer lugar está la función (u operador), que se usa para calcular el valor de la celda, luego hay una lista de celdas de las que depende esta celda: sus valores se pasan a la función. Una sintaxis tan extraña, a primera vista, tiene muchas ventajas, que consideraremos más adelante. Hasta ahora, la lógica de MRR aquí se asemeja al enfoque habitual con variables computables utilizadas en Vue, Svelte y otras bibliotecas, con la única diferencia de que puede utilizar funciones puras.

Implementamos la sustitución de la estación seleccionada de la lista en el campo de entrada. También es necesario ocultar la lista de estaciones después de que el usuario haga clic en una de ellas.

 const Tickets = withMrr({ $init: { stationFromOptions: [], stationFromInput: '', }, stationFromOptions: [str => stations.filter(s => str.indexOf(a) === 0), 'stationFromInput'], stationFrom: ['merge', 'stationFromInput', 'selectStationFrom'], optionsShown: ['toggle', 'stationFromInput', 'selectStationFrom'], }, (state, props, $) => { return (<div> <div>  : <input onChange={ $('stationFromInput') } value={ state.stationFrom }/> </div> { state.optionsShown && <ul className="stationFromOptions"> { state.stationFromOptions.map(s => <li onClick={ $('selectStationFrom', s) }>{ s }</li>) } </ul> } </div>); }); 

Un controlador de eventos creado usando $ helper en la lista de estaciones emitirá valores fijos para cada opción.

MRR es consistente en su enfoque declarativo, ajeno a cualquier mutación. Después de seleccionar una estación, no podemos "forzar" el cambio del valor de la celda. En su lugar, creamos una nueva celda stationFrom , que, utilizando el operador merge stream merge (un equivalente aproximado en Rx es combineLatest), recopilará los valores de dos flujos: entrada del usuario ( stationFromInput ) y selección de estación ( selectStationFrom ).

Debemos mostrar la lista de opciones después de que el usuario ingrese algo, y ocultarnos después de haber seleccionado una de las opciones. La celda optionsShown será responsable de la visibilidad de la lista de opciones, que tomará valores booleanos dependiendo de los cambios en otras celdas. Este es un patrón muy común para el que existe el azúcar sintáctico: el operador de alternancia . Establece el valor de la celda en verdadero en cualquier cambio del primer argumento (secuencia), y en falso , el segundo.

Agregue un botón para borrar el texto ingresado.

 const Tickets = withMrr({ $init: { stationFromOptions: [], stationFromInput: '', }, stationFromOptions: [str => stations.filter(s => str.indexOf(a) === 0), 'stationFromInput'], clearVal: [a => '', 'clear'], stationFrom: ['merge', 'stationFromInput', 'selectStationFrom', 'clearVal'], optionsShown: ['toggle', 'stationFromInput', 'selectStationFrom'], }, (state, props, $) => { return (<div> <div>  : <input onChange={ $('stationFromInput') } value={ state.stationFrom }/> { state.stationFrom && <button onClick={ $('clear') }></button> } </div> { state.optionsShown && <ul className="stationFromOptions"> { state.stationFromOptions.map(s => <li onClick={ $('selectStationFrom', s) }>{ s }</li>) } </ul> } </div>); }); 

Ahora, nuestra celda stationFrom , que es responsable del contenido del texto en el campo de entrada, recopila sus valores no de dos, sino de tres flujos. Este código puede ser simplificado. La construcción mrr de la forma [* fórmula *, * ... argumentos de celda *] es similar a las expresiones S en Lisp, y como en Lisp, puede anidar arbitrariamente tales construcciones entre sí.

Eliminemos la inútil celda clearVal y acortemos el código:

  stationFrom: ['merge', 'stationFromInput', 'selectStationFrom', [a => '', 'clear']], 

Los programas escritos en un estilo imperativo se pueden comparar con un equipo mal organizado, donde todos constantemente se ordenan algo entre sí (insinúo las llamadas a métodos y los cambios mutantes), además, ambos gerentes son subordinados y viceversa. Los programas declarativos son similares a la imagen utópica opuesta: un colectivo donde todos saben claramente cómo debe actuar en cualquier situación. No hay necesidad de órdenes en dicho equipo; todos están en sus lugares y trabajando en respuesta a lo que está sucediendo.

En lugar de describir todas las posibles consecuencias de un evento (léase, para hacer ciertas mutaciones), describimos todos los casos en los que este evento puede ocurrir, es decir. qué valor tomará la celda ante cualquier cambio en otras celdas. En nuestro pequeño ejemplo hasta ahora, hemos descrito la celda stationFrom y tres situaciones que afectan su valor. Para un programador acostumbrado al código imperativo, este enfoque puede parecer inusual (o incluso una "muleta", una "perversión"). De hecho, le permite ahorrar esfuerzo debido a la brevedad (y estabilidad) del código, como veremos en la práctica.

¿Qué pasa con la asincronía? ¿Es posible extraer la lista de estaciones propuestas con ajax? No hay problema! En esencia, para mrr no importa si la función devuelve un valor o una promesa. Cuando se devuelve mrr, esperará su resolución y "empujará" los datos recibidos en la secuencia.

 stationFromOptions: [str => fetch('/get_stations?str=' + str).then(res => res.toJSON()), 'stationFromInput'], 

También significa que puede usar funciones asincrónicas como fórmulas. Más adelante se considerarán casos más complejos (manejo de errores, estado de promesa).

La funcionalidad para elegir una estación de salida está lista. No tiene sentido duplicar lo mismo para la estación de llegada, vale la pena ponerlo en un componente separado que pueda reutilizarse. Este será un componente de entrada generalizado con autocompletado, por lo que cambiaremos el nombre de los campos y crearemos la función para obtener las opciones adecuadas establecidas en accesorios.

 const OptionsInput = withMrr(props => ({ $init: { options: [], }, val: ['merge', 'valInput', 'selectOption', [a => '', 'clear']], options: [props.getOptions, 'val'], optionsShown: ['toggle', 'valInput', 'selectOption'], }), (state, props, $) => <div> <div> <input onChange={ $('valInput') } value={ state.val } /> </div> { state.optionsShown && <ul className="options"> { state.options.map(s => <li onClick={ $('selectOption', s) }>{ s }</li>) } </ul> } { state.val && <div className="clear" onClick={ $('clear') }> X </div> } </div>) 

Como puede ver, puede especificar la estructura de las celdas mrr en función del componente de accesorios (sin embargo, se ejecutará solo una vez, luego de la inicialización, y no responderá a los cambios en los accesorios).

Comunicación entre componentes.


Ahora conecte este componente en el componente principal y vea cómo mrr permite que los componentes relacionados intercambien datos.

 const getMatchedStations = str => fetch('/get_stations?str=' + str).then(res => res.toJSON()); const Tickets = withMrr({ stationTo: 'selectStationFrom/val', stationFrom: 'selectStationTo/val', }, (state, props, $, connectAs) => { return (<div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div>); }); 

Para asociar un componente primario con un componente secundario, debemos pasarle parámetros mediante la función connectAs (cuarto argumento de la función de representación). En este caso, indicamos el nombre que queremos darle al componente hijo. Al adjuntar un componente de esta manera, con este nombre podemos acceder a sus celdas. En este caso, estamos escuchando células val. También es posible lo contrario: escuche desde el componente secundario de la celda principal.

Como puede ver, mrr sigue un enfoque declarativo: no se necesitan devoluciones de llamada onChange, solo necesitamos especificar un nombre para el componente hijo en la función connectAs, ¡después de lo cual tenemos acceso a sus celdas! En este caso, nuevamente debido a la declaratividad, no hay amenaza de interferencia en el trabajo de otro componente: no tenemos la capacidad de "cambiar" nada en él, mutar, solo podemos "escuchar" los datos.

Señales y valores


La siguiente etapa es la búsqueda de trenes adecuados para los parámetros seleccionados. En el enfoque imperativo, probablemente escribiríamos un determinado procesador para enviar el formulario onSubmit, que iniciaría otras acciones: una solicitud ajax y la visualización de los resultados. Pero, como recordará, ¡no podemos "ordenar" nada! Solo podemos crear otro conjunto de celdas derivadas de las celdas del formulario. Escribamos una solicitud más.

 const getTrains = (from, to, date) => fetch('/get_trains?from=' + from + '&to=' + to + '&date=' + date).then(res => res.toJSON()); const Tickets = withMrr({ stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', results: [getTrains, 'stationFrom', 'stationTo', 'date', 'searchTrains'], }, (state, props, $, connectAs) => { return (<div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div>); }); 

Sin embargo, dicho código no funcionará como se esperaba. El cálculo (recálculo del valor de la celda) se inicia cuando cambia cualquiera de los argumentos, por lo que la solicitud se enviará, por ejemplo, inmediatamente después de seleccionar la primera estación, y no simplemente haciendo clic en "Buscar". Necesitamos, por un lado, que las estaciones y la fecha se pasen a los argumentos de la fórmula, pero por otro lado, no respondan a su cambio. En mrr hay un mecanismo elegante para esto llamado escucha pasiva.

  results: [getTrains, '-stationFrom', '-stationTo', '-date', 'searchTrains'], 

Simplemente agregue un signo menos delante del nombre de la celda y ¡listo! Ahora, los resultados solo responderán a los cambios en la celda searchTrains .

En este caso, la celda searchTrains actúa como una "señal de celda", y las celdas de stationFrom y otras como "valores de celda". Para una celda de señal, solo es importante el momento en que los valores "fluyen" a través de ella y qué tipo de datos serán, de todos modos: pueden ser verdaderos, "1" o lo que sea (en nuestro caso, serán objetos de Evento DOM) ) Para una celda de valor, lo importante es su valor, pero al mismo tiempo, los momentos de su cambio no son significativos. Estos dos tipos de celdas no son mutuamente excluyentes: muchas celdas son tanto señales como valores. En el nivel de sintaxis en mrr, estos dos tipos de celdas no difieren de ninguna manera, pero la comprensión conceptual de tal diferencia es muy importante al escribir código reactivo.

División de corrientes


Una solicitud para buscar asientos en el tren puede llevar algún tiempo, por lo que debemos mostrar el cargador y también responder en caso de error. Ya hay pocas promesas para este enfoque predeterminado con resolución automática.

 const Tickets = withMrr({ $init: { results: {}, } stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', searchQuery: [(from, to, date) => ({ from, to, date }), '-stationFrom', '-stationTo', '-date', 'searchTrains'], results: ['nested', (cb, query) => { cb({ loading: true, error: null, data: null }); getTrains(query.from, query.to, query.date) .then(res => cb('data', res)) .catch(err => cb('error', err)) .finally(() => cb('loading', false)) }, 'searchQuery'], availableTrains: 'results.data', }, (state, props, $, connectAs) => { return (<div> <div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div> <div> { state.results.loading && <div className="loading">...</div> } { state.results.error && <div className="error"> . ,  .   .</div> } { state.availableTrains && <div className="results"> { state.availableTrains.map((train) => <div />) } </div> } </div> </div>); }); 

El operador anidado le permite "descomponer" los datos en subcélulas; para esto, el primer argumento de la fórmula es la devolución de llamada, con la que puede "empujar" los datos en la subcelda (una o más). Ahora tenemos transmisiones separadas que son responsables del error, el estado de la promesa y los datos recibidos. El operador anidado es una herramienta muy poderosa y uno de los pocos imperativos en mrr (nosotros mismos especificamos en qué celdas colocar los datos). Mientras que el operador de fusión combina múltiples subprocesos en uno, anidado divide el subproceso en múltiples subprocesos, siendo lo contrario.

El ejemplo anterior es una forma estándar de trabajar con promesas, en mrr se generaliza como un operador de promesa y le permite acortar el código:

  results: ['promise', (query) => getTrains(query.from, query.to, query.date), 'searchQuery'], //     availableTrains: 'results.data', 

Además, el operador de la promesa se asegura de que solo se utilicen los resultados de la promesa más reciente.



Componente para mostrar los asientos disponibles (por simplicidad, rechazaremos los diferentes tipos de automóviles)

 const TrainSeats = withMrr({ selectSeats: [(seatsNumber, { id }) => new Array(Number(seatsNumber)).fill(true).map(() => ({ trainId: id })), '-seatsNumber', '-$props', 'select'], seatsNumber: [() => 0, 'selectSeats'], }, (state, props, $) => <div className="train">  №{ props.num } { props.from } - { props.to }.   : { props.seats || 0 } { props.seats && <div>     : <input type="number" onChange={ $('seatsNumber') } value={ state.seatsNumber || 0 } max={ props.seats } /> <button onClick={ $('select') }></button> </div> } </div>); 

Para acceder a los accesorios en una fórmula, puede suscribirse al cuadro especial $ props.

 const Tickets = withMrr({ ... selectedSeats: '*/selectSeats', }, (state, props, $, connectAs) => { ... <div className="results"> { state.availableTrains.map((train, i) => <TrainSeats key={i} {...train} {...connectAs('train' + i)}/>) } </div> } 

De nuevo, usamos la escucha pasiva para recoger el número de lugares seleccionados cuando hace clic en el botón "Seleccionar". Asociamos cada componente hijo con el padre utilizando la función connectAs. El usuario puede seleccionar asientos en cualquiera de los trenes propuestos, por lo que escuchamos los cambios en todos los componentes secundarios utilizando la máscara "*".

Pero aquí está el problema: el usuario puede agregar asientos primero en un tren, luego en otro, para que la nueva información mueva a los anteriores. ¿Cómo "acumular" datos de flujo? Para hacer esto, hay un operador de cierre , que, junto con anidado y embudo, forma la base de mrr (todos los demás no son más que azúcar sintáctica basada en estos tres).

  selectedSeats: ['closure', () => { let seats = []; //     return selectedSeats => { seats = [...seats, selectedSeats]; return seats; } }, '*/selectSeats'], 

Cuando se usa el cierre primero (en componentDidMount), se crea un cierre que devuelve la fórmula. Ella tiene así acceso a las variables de cierre. Esto le permite guardar datos entre llamadas de manera segura, sin caer en el abismo de las variables globales y el estado mutable compartido. Por lo tanto, el cierre le permite implementar la funcionalidad de los operadores de Rx, como el escaneo y otros. Sin embargo, este método es bueno para casos difíciles. Si solo necesitamos guardar el valor de una variable, simplemente podemos usar el enlace al valor anterior de la celda usando el nombre especial "^":

  selectedSeats: [(seats, prev) => [...seats, ...prev], '*/selectSeats', '^'] 

Ahora el usuario debe ingresar un nombre y apellido para cada boleto seleccionado.

 const SeatDetails = withMrr({}, (state, props, $) => { return (<div> { props.trainId } <input name="name" value={ props.name } onChange={ $('setDetails', e => ['name', e.target.value, props.i]) } /> <input name="surname" value={ props.surname } onChange={ $('setDetails', e => ['surname', e.target.value, props.i]) }/> <a href="#" onClick={ $('removeSeat', props.i) }>X</a> </div>); }) const Tickets = withMrr({ $init: { results: {}, selectedSeats: [], } stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', searchQuery: [(from, to, date) => ({ from, to, date }), '-stationFrom', '-stationTo', '-date', 'searchTrains'], results: ['promise', (query) => getTrains(query.from, query.to, query.date), 'searchQuery'], availableTrains: 'results.data', selectedSeats: [(seats, prev) => [...seats, ...prev], '*/selectSeats', '^'] }, (state, props, $, connectAs) => { return (<div> <div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div> <div> { state.results.loading && <div className="loading">...</div> } { state.results.error && <div className="error"> . ,  .   .</div> } { state.availableTrains && <div className="results"> { state.availableTrains.map((train, i) => <TrainSeats key={i} {...train} {...connectAs('train' + i)}/>) } </div> } { state.selectedSeats.map((seat, i) => <SeatDetails key={i} i={i} { ...seat } {...connectAs('seat' + i)}/>) } </div> </div>); }); 

La celda selectedSeats contiene una matriz de ubicaciones seleccionadas. A medida que el usuario ingresa el nombre y apellido de cada ticket, debemos cambiar los datos en los elementos correspondientes de la matriz.

  selectedSeats: [(seats, details, prev) => { // ??? }, '*/selectSeats', '*/setDetails', '^'] 

El enfoque estándar no es adecuado para nosotros: en la fórmula, necesitamos saber qué celda ha cambiado y responder en consecuencia. Una de las formas del operador de fusión nos ayudará.

  selectedSeats: ['merge', { '*/selectSeats': (seats, prev) => { return [...prev, ...seats]; }, '*/setDetails': ([field, value, i], prev) => { prev[i][field] = value; return prev; }, '*/removeSeat': (i, prev) => { prev.splice(i, 1); return prev; }, }, '^'/*,       */], 

Esto es un poco como los reductores de Redux, pero con una sintaxis más flexible y potente. Y no puede tener miedo de mutar la matriz, porque solo la fórmula de una celda tiene control sobre ella, respectivamente, se excluyen los cambios paralelos (pero la mutación de matrices que se pasan como argumentos ciertamente no vale la pena).

Colecciones reactivas


El patrón cuando la celda almacena y cambia la matriz es muy común. Todas las operaciones con la matriz son de tres tipos: insertar, modificar, eliminar. Para describir esto, hay un elegante operador de coll . Úselo para simplificar el cálculo de los asientos seleccionados .

Fue:

  selectedSeats: ['merge', { '*/selectSeats': (seats, prev) => { return [...prev, ...seats]; }, '*/setDetails': ([field, value, i], prev) => { prev[i][field] = value; return prev; }, '*/removeSeat': (i, prev) => { prev.splice(i, 1); return prev; }, 'addToCart': () => [], }, '^'] 

se convirtió en:

  selectedSeats: ['coll', { create: '*/selectSeats', update: '*/setDetails', delete: ['merge', '*/removeSeat', [() => ({}), 'addToCart']] }] 

sin embargo, el formato de datos en la secuencia setDetails debe modificarse un poco:

  <input name="name" onChange={ $('setDetails', e => [{ name: e.target.value }, props.i]) } /> <input name="surname" onChange={ $('setDetails', e => [{ surname: e.target.value }, props.i]) }/> 

Usando el operador coll , describimos tres hilos que afectarán nuestra matriz. En este caso, la secuencia de creación debe contener los propios elementos, que deben agregarse a la matriz (generalmente objetos). La secuencia de eliminación acepta los índices de los elementos que se eliminarán (tanto en '* / removeSeat') como las máscaras. La máscara {} eliminará todos los elementos y, por ejemplo, la máscara {nombre: 'Carl'} eliminará todos los elementos con el nombre Carl. La secuencia de actualización acepta pares de valores: el cambio que debe hacerse con el elemento (máscara o función) y el índice o máscara de los elementos que deben cambiarse. Por ejemplo, [{apellido: 'Johnson'}, {}] establecerá el apellido Johnson en todos los elementos de la matriz.

El operador coll usa algo así como un lenguaje de consulta interno, lo que facilita el trabajo con colecciones y lo hace más declarativo.

El código completo de nuestra aplicación en JsFiddle.

Nos familiarizamos con casi todas las funciones básicas necesarias de MRR. Un tema bastante significativo que se ha mantenido por la borda es la gestión global de la riqueza, que se discutirá en futuros artículos. Pero ahora puede comenzar a usar mrr para administrar el estado dentro de un componente o grupo de componentes relacionados.

Conclusiones


¿Cuál es el poder de mrr?


mrr le permite escribir aplicaciones en React en un estilo funcional-reactivo (mrr se puede descifrar como Make React Reactive). MRR es muy expresivo: pasa menos tiempo escribiendo líneas de código.

MRR proporciona un pequeño conjunto de abstracciones básicas, que es suficiente para todos los casos: este artículo describe casi todas las características y técnicas principales de MRR.También hay herramientas para expandir este conjunto básico (la capacidad de crear operadores personalizados). Podrá escribir un hermoso código declarativo sin leer cientos de páginas del manual y sin siquiera estudiar las profundidades teóricas de la programación funcional; es poco probable que tenga que usar, por ejemplo, mónadas, porque MRR es una mónada gigante que separa el cómputo puro de las mutaciones estatales.

Mientras que en otras bibliotecas a menudo coexisten enfoques heterogéneos (imperativo usando métodos y declarativo usando aglutinantes reactivos), de los cuales el programador mezcla aleatoriamente "ensalada", en mrr hay una esencia básica única: una corriente, que contribuye a la homogeneidad y uniformidad del código. La comodidad, la conveniencia, la simplicidad, el ahorro de tiempo de un programador son las principales ventajas de mrr (a partir de aquí hay otra decodificación de mrr como "mrrrr", es decir, ronronear a un gato que está satisfecho con la vida de un gato).

¿Cuáles son las desventajas?


La programación con "líneas" tiene sus ventajas y desventajas. No podrá completar automáticamente el nombre de la celda ni buscar el lugar donde está definida. Por otro lado, en mrr siempre hay un único lugar donde se determina el comportamiento de la celda, y es fácil encontrarlo con una simple búsqueda de texto, mientras se busca el lugar donde se determina el valor del campo Redux de la tienda, o incluso menos el campo de estado cuando se usa el setState nativo. Puede ser más largo.

¿Quién podría estar interesado en esto?


En primer lugar, los partidarios de la programación funcional son personas para quienes la ventaja de un enfoque declarativo es obvia. Por supuesto, las soluciones kosher ClojureScript ya existen, pero siguen siendo un producto de nicho, mientras que React gobierna la pelota. Si su proyecto ya usa Redux, puede comenzar a usar mrr para administrar el estado local y, en el futuro, pasar a global. Incluso si no planea utilizar nuevas tecnologías en este momento, puede lidiar con la MRR para “estirar su cerebro” al observar las tareas familiares desde una nueva perspectiva, porque MRR es significativamente diferente de las bibliotecas comunes de administración de estado.

¿Se puede usar esto ya?


En principio, sí :) La biblioteca es joven, hasta ahora se ha utilizado activamente en varios proyectos, pero la API de la funcionalidad básica ya se ha establecido, ahora se está trabajando principalmente en diferentes lociones (azúcar sintáctico), diseñadas para acelerar aún más y facilitar el desarrollo. Por cierto, en los principios de MRR no hay nada específico para React, se puede adaptar para usar con cualquier biblioteca de componentes (React se eligió debido a la falta de reactividad incorporada o una biblioteca generalmente aceptada para esto).

¡Gracias por su atención, le agradeceré sus comentarios y críticas constructivas!

Source: https://habr.com/ru/post/es428870/


All Articles