Cómo crear una tienda usando funciones puras

¡Descubrir JavaScript funcional fue nombrado uno de los mejores libros nuevos de programación funcional por BookAuthority!

Foto de Ugur Akdemir en Unsplash

Las funciones puras producen el mismo valor de salida, dada la misma entrada. No tienen efectos secundarios y son más fáciles de leer, comprender y probar.

Dado todo esto, me gustaría crear una tienda que oculte el estado pero al mismo tiempo utilice funciones puras.

Inmutabilidad

Las funciones puras no modifican su entrada. Tratan los valores de entrada como inmutables.

Un valor inmutable es un valor que, una vez creado, no se puede cambiar.

Immutable.js proporciona estructuras de datos inmutables como List. Una estructura de datos inmutable creará una nueva estructura de datos en cada acción.

Considere el siguiente código:

importar {Lista} desde "inmutable";
const list = List ();
const newList = list.push (1);

push () crea una nueva lista que tiene el nuevo elemento. No modifica la lista existente.

delete () devuelve una nueva Lista donde se eliminó el elemento en el índice especificado.

La estructura de datos de la Lista ofrece una interfaz agradable para trabajar con listas de manera inmutable, por lo que la usaré como valor de estado.

La tienda

La tienda gestiona el estado.

El estado es información que puede cambiar. La tienda oculta los datos del estado y ofrece un conjunto público de métodos para trabajar con ellos.

Me gustaría crear una librería con los métodos add (), remove () y getBy ().

Quiero que todas estas funciones sean funciones puras.

Habrá dos tipos de funciones puras utilizadas por la tienda:

  • funciones que leerán y filtrarán el estado. Los llamaré captadores.
  • funciones que modificarán el estado. Los llamaré setters.

Ambos tipos de funciones tomarán el estado como su primer parámetro.

Escribamos la tienda usando funciones puras.

importar {Lista} desde "inmutable";
importar parcial de "lodash / parcial";
importar matchProperty desde "lodash / matchProperty";
// setters
función add (libros, libro) {
  return books.push (libro);
}
función eliminar (libros, libro) {
  const index = books.findIndex (coincide con la propiedad ("id", book.id));
  return books.delete (index);
}
// getters
función queryContainsBook (consulta, libro) {
  if (consulta && query.text) {
    volver book.title.includes (query.text);
  }
  volver verdadero;
}
función getBy (libros, consulta) {
  return books.filter (parcial (queryContainsBook, query)). toArray ();
}

La biblioteca

El estado debe estar oculto dentro del objeto de la tienda. No quiero enviar el estado desde el exterior al objeto de la tienda. Al mismo tiempo, quiero obtener todos los beneficios de las funciones puras y usarlas para definir los métodos de almacenamiento.

¿Cómo se puede lograr esto?

Hemos visto la respuesta en Redux. Escribimos funciones puras y dejamos que la biblioteca cree la tienda y aplique las funciones puras.

Así es como me gustaría usar la biblioteca para definir la tienda:

importar {Lista} desde "inmutable";
importar tienda desde "./Store-toolbox";
// setters
función add (libros, libro) {}
función eliminar (libros, libro) {}
// getters
función getBy (libros, consulta) {}
exportar tienda predeterminada ({
  estado: Lista (),
  setters: {agregar, eliminar},
  captadores: {getBy}
});

Construyamos esta microbiblioteca que crea la tienda basada en los captadores y creadores puros.

Todos los captadores y colocadores públicos serán decorados y recibirán el estado como su primer parámetro.

  • El valor de retorno de los captadores se devolverá a las funciones del llamador.
  • El valor devuelto por stetters se usará para cambiar el estado. Las funciones de la persona que llama no recibirán el nuevo estado.
función decorateMethods (obj, decorator) {
  let newObject = {... obj};
  Object.keys (newObject) .forEach (function decorateMethod (fnName) {
    if (typeof newObject [fnName] === "function") {
      newObject [fnName] = decorador (newObject [fnName]);
    }
  });
  return newObject;
}
función Store (storeConfig) {
  función de retorno () {
    let state = storeConfig.state;
    configurador de funciones (fn) {
      función de retorno (... args) {
        estado = fn (estado, ... args);
      };
    }
    función getter (fn) {
      función de retorno (... args) {
        return fn (estado, ... args);
      };
    }
    return Object.freeze ({
      ... decorateMethods (storeConfig.getters, getter),
      ... decorateMethods (storeConfig.setters, setter)
    });
  };
}
exportar tienda predeterminada;

Store () crea una función que devuelve un objeto que encapsula el estado.

Vamos a crear y usar el objeto bookStore:

importar BookStore desde "./BookStore";
const bookStore = BookStore ();
bookStore.add ({id: 1, título: "Cómo funciona JavaScript"});

Al llamar a bookStore.add ({}), el decorador de configuración llamará a la función de configuración pura add () con el estado actual como primer parámetro y el nuevo libro como segundo parámetro. Luego, el decorador de setter usará el resultado para cambiar el valor del estado.

El objeto

Analicemos el objeto bookStore.

Solo expone tres métodos, todas las demás funciones puras son privadas.

La interfaz pública del objeto no se puede modificar desde el exterior.

El estado está oculto. Los clientes que usan el objeto bookStore pueden acceder al estado solo a través de los métodos públicos.

Conclusión

Las funciones puras son más fáciles de razonar.

Podemos crear una tienda que oculta el estado y utiliza funciones puras con la ayuda de una biblioteca.

Podemos escribir solo las funciones puras y dejar que la biblioteca las aplique y haga la mutación.

Puede consultar el código de muestra en codesandbox.io.

¡Descubrir JavaScript funcional fue nombrado uno de los mejores libros nuevos de programación funcional por BookAuthority!

Para obtener más información sobre la aplicación de técnicas funcionales a React, eche un vistazo a Functional React.

Aprenda a aplicar los Principios de los patrones de diseño.

Puedes encontrarme en Twitter.