Al explorar las características de inyección de dependencias en servicios de Angular y el uso de flujos observables en RxJS, decidí aplicar estas ideas a un proyecto de React.
Para comenzar, necesitamos:
- Un proyecto de React vacío.
- Instalar RxJS, ejecutando
npm install rxjs --save.
Enfoque de Implementación
- Según los requisitos del negocio, encapsular los estados globales necesarios en diferentes clases de servicio para lograr una separación de datos.
- Crear un proveedor personalizado y un método de conexión para inyectar los estados globales y servicios en los componentes.
- Escribir
mapStateToPropsen los componentes para especificar qué servicios y estados globales se desean obtener.
Código Ejemplo
// Envolver la clase de servicio para crear una instancia única
const encapsularServicio = (Servicio, datosIniciales = {}) => {
const servicio = new Servicio(datosIniciales);
return () => servicio;
}
// Clase de servicio
import { Subject } from 'rxjs';
import { encapsularServicio } from 'Utils/servicios';
class ServicioPrueba {
constructor(datosIniciales = {}) {
this.datos = datosIniciales;
}
datos$ = new Subject();
suscribirse = (callback) => {
this.suscripcion = this.datos$.subscribe(callback);
this.datos$.next(this.datos);
return () => {
this.suscripcion.unsubscribe();
}
}
agregar = (datos) => {
this.datos.contador = Number(datos.contador) + Number(this.datos.contador);
this.datos$.next(this.datos);
}
establecer = (datos) => {
this.datos = { ...this.datos, ...datos };
this.datos$.next(this.datos);
}
}
const datosIniciales = {
contador: 0,
prueba1: 'Hola',
prueba2: 'Mundo'
};
export default encapsularServicio(ServicioPrueba, datosIniciales)();
// Obtener un contexto único
const obtenerContexto = (() => {
const Contexto = React.createContext(null);
Contexto.displayName = 'proveedorDeServicios';
return () => Contexto;
})();
// Inyectar todos los estados globales por defecto
const mapStateToPropsPorDefecto = (datos) => ({
...datos
});
/**
* Componente de orden superior para inyectar servicios
*/
function ProveedorDeServicios({ servicios = {}, hijos = null }) {
const Contexto = obtenerContexto();
const [valorContexto, actualizarValorContexto] = useState({
...servicios
});
const listaDesuscripciones = [];
useEffect(() => {
Object.keys(servicios).forEach((clave) => {
const desuscribirse = servicios[clave].suscribirse(_datos => {
actualizarValorContexto({
...valorContexto,
..._datos
});
});
listaDesuscripciones.push(desuscribirse);
});
return () => {
listaDesuscripciones.forEach(desuscribirse => {
typeof desuscribirse === 'function' && desuscribirse();
});
}
}, []);
return (
<Contexto.Provider valor={valorContexto}>{hijos}</Contexto.Provider>
);
}
// Inyectar estados globales y objetos de servicio en los componentes
const inyectar = (mapStateToProps = mapStateToPropsPorDefecto) => {
const Contexto = obtenerContexto();
return (Componente) => (props) => (
<Contexto.Consumer>
{datos => {
const nuevasProps = mapStateToProps(datos);
return <Componente {...props} {...nuevasProps} />
}}
</Contexto.Consumer>
);
}
// Uso: envolver el componente principal con ProveedorDeServicios
ReactDOM.render(
<ProveedorDeServicios servicios={{ servicioPrueba: servicioPrueba }}>
<Indice />
</ProveedorDeServicios>,
document.getElementById("root")
);
// En los componentes necesarios, usar el método inyectar
const mapStateToProps = (datos) => {
const {
contador,
servicioPrueba
} = datos;
return {
contador,
servicioPrueba
};
}
export default inyectar(mapStateToProps)(Suscriptor);
// Ahora el componente Suscriptor tiene acceso a los estados globales contador y al servicio servicioPrueba a través de las props.
Este enfoque permite una mejor comprensión de los componentes de orden superior y el uso de Context en React, simplificando el uso en comparación con Redux tradicional al combinar mapStateToProps y mapDispatchToProps. Se ha empaquetado esta funcionalidad como un módulo npm bajo el nombre service-redux, disponible en este enlace.