Tener servicios en la aplicación React
Vengo del mundo angular donde podría extraer lógica a un servicio/fábrica y consumirla en mis controladores.
Estoy tratando de entender cómo puedo lograr lo mismo en una aplicación React.
Digamos que tengo un componente que valida la entrada de contraseña del usuario (su seguridad). Su lógica es bastante compleja, por lo que no quiero escribirla en el componente.
¿Dónde debería escribir esta lógica? ¿En una tienda si estoy usando flux? ¿O hay una mejor opción?
El problema se vuelve extremadamente simple cuando te das cuenta de que un servicio Angular es solo un objeto que ofrece un conjunto de métodos independientes del contexto. Es solo el mecanismo Angular DI lo que hace que parezca más complicado. El DI es útil ya que se encarga de crear y mantener instancias por usted, pero realmente no lo necesita.
Considere una biblioteca AJAX popular llamada axios (de la que probablemente haya oído hablar):
import axios from "axios";
axios.post(...);
¿No se comporta como un servicio? Proporciona un conjunto de métodos responsables de una lógica específica y es independiente del código principal.
Su caso de ejemplo fue sobre la creación de un conjunto aislado de métodos para validar sus entradas (por ejemplo, verificar la seguridad de la contraseña). Algunos sugirieron poner estos métodos dentro de los componentes, lo que para mí es claramente un antipatrón. ¿Qué pasa si la validación implica realizar y procesar llamadas de backend XHR o realizar cálculos complejos? ¿Combinaría esta lógica con controladores de clic del mouse y otras cosas específicas de la interfaz de usuario? Disparates. Lo mismo ocurre con el enfoque contenedor/HOC. ¿Envolver su componente solo para agregar un método que verificará si el valor tiene un dígito? Vamos.
Simplemente crearía un nuevo archivo llamado 'ValidationService.js' y lo organizaría de la siguiente manera:
const ValidationService = {
firstValidationMethod: function(value) {
//inspect the value
},
secondValidationMethod: function(value) {
//inspect the value
}
};
export default ValidationService;
Luego en tu componente:
import ValidationService from "./services/ValidationService.js";
...
//inside the component
yourInputChangeHandler(event) {
if(!ValidationService.firstValidationMethod(event.target.value) {
//show a validation warning
return false;
}
//proceed
}
Utilice este servicio desde cualquier lugar que desee. Si las reglas de validación cambian, debe centrarse únicamente en el archivo ValidationService.js.
Es posible que necesite un servicio más complicado que dependa de otros servicios. En este caso, su archivo de servicio puede devolver un constructor de clase en lugar de un objeto estático para que pueda crear una instancia del objeto usted mismo en el componente. También puede considerar implementar un singleton simple para asegurarse de que siempre haya una sola instancia del objeto de servicio en uso en toda la aplicación.
La primera respuesta no refleja el paradigma actual de Contenedor vs Presentador .
Si necesita hacer algo, como validar una contraseña, probablemente tenga una función que lo haga. Estarías pasando esa función a tu vista reutilizable como accesorio.
Contenedores
Entonces, la forma correcta de hacerlo es escribir un ValidatorContainer, que tendrá esa función como propiedad, y envolver el formulario en él, pasando los accesorios correctos al niño. Cuando se trata de su vista, su contenedor validador envuelve su vista y la vista consume la lógica del contenedor.
La validación se puede realizar en las propiedades del contenedor, pero si usa un validador de terceros o cualquier servicio de validación simple, puede usar el servicio como una propiedad del componente del contenedor y usarlo en los métodos del contenedor. He hecho esto para componentes relajantes y funciona muy bien.
Proveedores
Si es necesaria un poco más de configuración, puede utilizar un modelo de Proveedor/Consumidor. Un proveedor es un componente de alto nivel que se envuelve en algún lugar cerca y debajo del objeto de aplicación superior (el que usted monta) y proporciona una parte de sí mismo, o una propiedad configurada en la capa superior, a la API de contexto. Luego configuro los elementos de mi contenedor para consumir el contexto.
Las relaciones de contexto padre/hijo no tienen que estar cerca una de otra, sólo que el niño tiene que descender de alguna manera. Las tiendas Redux y el React Router funcionan de esta manera. Lo he usado para proporcionar un contexto raíz de descanso para mis contenedores de descanso (si no proporciono el mío propio).
(nota: la API de contexto está marcada como experimental en los documentos, pero no creo que ya lo sea, considerando para qué la usa).
//An example of a Provider component, takes a preconfigured restful.js
//object and makes it available anywhere in the application
export default class RestfulProvider extends React.Component {
constructor(props){
super(props);
if(!("restful" in props)){
throw Error("Restful service must be provided");
}
}
getChildContext(){
return {
api: this.props.restful
};
}
render() {
return this.props.children;
}
}
RestfulProvider.childContextTypes = {
api: React.PropTypes.object
};
software intermedio
Otra forma que no he probado, pero que he visto utilizada, es utilizar middleware junto con Redux. Usted define su objeto de servicio fuera de la aplicación, o al menos, por encima del almacén redux. Durante la creación de la tienda, usted inyecta el servicio en el middleware y el middleware maneja cualquier acción que afecte al servicio.
De esta manera, podría inyectar mi objeto restful.js en el middleware y reemplazar mis métodos de contenedor con acciones independientes. Todavía necesitaría un componente contenedor para proporcionar las acciones a la capa de vista del formulario, pero connect() y mapDispatchToProps me cubren allí.
El nuevo reaccionar-router-redux v4 utiliza este método para impactar el estado del historial, por ejemplo.
//Example middleware from react-router-redux
//History is our service here and actions change it.
import { CALL_HISTORY_METHOD } from './actions'
/**
* This middleware captures CALL_HISTORY_METHOD actions to redirect to the
* provided history object. This will prevent these actions from reaching your
* reducer or any middleware that comes after this one.
*/
export default function routerMiddleware(history) {
return () => next => action => {
if (action.type !== CALL_HISTORY_METHOD) {
return next(action)
}
const { payload: { method, args } } = action
history[method](...args)
}
}
Necesitaba que alguna lógica de formato fuera compartida entre múltiples componentes y, como desarrollador de Angular, también me inclinaba naturalmente hacia un servicio.
Compartí la lógica poniéndola en un archivo separado.
function format(input) {
//convert input to output
return output;
}
module.exports = {
format: format
};
y luego lo importó como un módulo
import formatter from '../services/formatter.service';
//then in component
render() {
return formatter.format(this.props.data);
}