Tener servicios en la aplicación React

177

Vengo del mundo angular donde podría extraer lógica a un servicio / fábrica y consumirlos 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 (es fuerza). Su lógica es bastante compleja, por lo tanto, no quiero escribirla en el componente.

¿Dónde debo escribir esta lógica? ¿En una tienda si estoy usando flux? ¿O hay una mejor opción?

Dennis Nerush
fuente
Podría usar un paquete y ver cómo lo están haciendo - npmjs.com/package/react-password-strength-meter
James111
11
La seguridad de la contraseña es solo un ejemplo. Estoy buscando una mejor práctica más general
Dennis Nerush
Puede que tenga que hacerlo del lado del servidor?
James111
2
No. Solo la lógica del lado del cliente que no debería estar directamente en el componente. El comprobador de
seguridad de la
44
Si tiene muchas de estas funciones, puede almacenarlas en un archivo auxiliar y simplemente requerirlas en su archivo componente para su uso. Si es una función única que es relevante únicamente para ese componente, probablemente debería vivir allí sin importar la complejidad.
Jesse Kernaghan

Respuestas:

61

La primera respuesta no refleja el paradigma actual de Contenedor vs Presentador .

Si necesita hacer algo, como validar una contraseña, es probable que tenga una función que lo haga. Estaría pasando esa función a su vista reutilizable como accesorio.

Contenedores

Entonces, la forma correcta de hacerlo es escribir un ValidatorContainer, que tendrá esa función como una propiedad, y envolver el formulario en él, pasando los accesorios correctos al niño. Cuando se trata de su vista, su contenedor de validación envuelve su vista y la vista consume la lógica de los contenedores.

La validación se puede hacer todo en las propiedades del contenedor, pero si está utilizando un validador de terceros o cualquier servicio de validación simple, puede usar el servicio como una propiedad del componente contenedor y usarlo en los métodos del contenedor. He hecho esto para componentes relajantes y funciona muy bien.

Proveedores

Si se necesita un poco más de configuración, puede usar un modelo de proveedor / consumidor. Un proveedor es un componente de alto nivel que se ajusta en algún lugar cerca y debajo del objeto superior de la aplicación (el que monta) y proporciona una parte de sí mismo, o una propiedad configurada en la capa superior, a la API de contexto. Luego configuro mis elementos contenedores para consumir el contexto.

Las relaciones de contexto padre / hijo no tienen que estar cerca una de la otra, solo el hijo tiene que descender de alguna manera. Las tiendas Redux y el React Router funcionan de esta manera. Lo he usado para proporcionar un contexto de descanso raíz para mis contenedores de descanso (si no proporciono el mío).

(nota: la API de contexto está marcada como experimental en los documentos, pero no creo que sea más, considerando lo que la está usando).

//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
};

Middleware

Otra forma en que no lo he intentado, pero que he visto usado, es usar middleware junto con Redux. Defina su objeto de servicio fuera de la aplicación, o al menos, más alto que el almacén redux. Durante la creación de la tienda, 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 de formulario, pero connect () y mapDispatchToProps me tienen cubierto allí.

El nuevo v4 react-router-redux utiliza este método para afectar 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)
  }
}

afenina
fuente
gran respuesta amigo, me dejaste de hacer cosas sin cerebro 8) ¡¡¡KUDOS !!
csomakk
¿Cuál es el uso para el ejemplo de contenedor?
sensei
No lo estoy recomendando, pero si desea seguir el camino del localizador de servicios (algo similar a Angular), puede agregar algún tipo de proveedor de "inyector / contenedor" del que resuelva los servicios (habiéndolos registrado previamente).
eddiewould
Reaccionar ganchos viene al rescate. Con Hooks puedes escribir lógica reutilizable sin escribir una clase. reactjs.org/docs/…
Raja Malik
102

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 lo hace ver más complicado. El DI es útil ya que se encarga de crear y mantener instancias para 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 cierta 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é sucede si la validación implica hacer y procesar llamadas de back-end XHR o hacer cálculos complejos? ¿Mezclarías esta lógica con los controladores de clic del mouse y otras cosas específicas de la interfaz de usuario? Disparates. Lo mismo con el enfoque contenedor / HOC. ¿Está ajustando su componente solo para agregar un método que verificará si el valor tiene un dígito? Venga.

Simplemente crearía un nuevo archivo llamado say '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 su 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
}

Use 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 depende 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.

Wojtek Majerski
fuente
3
Esta es la forma en que yo también lo haría. Estoy bastante sorprendido de que esta respuesta tenga tan pocos votos, ya que parece ser el camino con menos fricción. Si su servicio depende de otros servicios, nuevamente, estaría importando esos otros servicios a través de sus módulos. Además, los módulos son, por definición, singletons, por lo que en realidad no se necesita más trabajo para "implementarlo como un singleton simple" - obtienes ese comportamiento gratis :)
Mickey Puri
66
+1: buena respuesta si solo está utilizando servicios que proporcionan funciones. Sin embargo , el servicio de Angular son clases que se definen una vez, lo que proporciona más características que solo entregar funciones. Puede almacenar en caché los objetos como parámetro de clase de servicio, por ejemplo.
Nino Filiu el
66
Esta debería ser la respuesta real, y no la respuesta complicada anterior
usuario1807334
1
Esta es una buena respuesta, excepto que no es "reactiva". El DOM no se actualizará en los cambios variables dentro del servicio.
Defacto
9
¿Qué pasa con la inyección de dependencia? Es imposible burlarse del servicio en su componente a menos que lo inyecte de alguna manera. Quizás tener un objeto global de "contenedor" de nivel superior que tenga cada servicio como un campo podría solucionar esto. Luego, en sus pruebas, puede anular los campos del contenedor con simulacros para los servicios que desea simular.
menehune23
34

Necesitaba un poco de lógica de formato para compartir entre múltiples componentes y, como desarrollador de Angular, también se inclinó naturalmente hacia un servicio.

Compartí la lógica colocá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);
    }
Kildareflare
fuente
8
Esta es una buena idea, como se mencionó en el documento React: reactjs.org/docs/composition-vs-inheritance.html Si desea reutilizar la funcionalidad que no es UI entre componentes, le sugerimos que la extraiga en un módulo JavaScript separado. Los componentes pueden importarlo y usar esa función, objeto o clase, sin extenderlo.
user3426603
Esa es realmente la única respuesta aquí que tiene sentido.
Artem Novikov
33

Tenga en cuenta que el propósito de React es unir mejor las cosas que lógicamente deberían combinarse. Si está diseñando un método complicado de "validar contraseña", ¿dónde se debe acoplar?

Bueno, tendrá que usarlo cada vez que el usuario necesite ingresar una nueva contraseña. Esto podría estar en la pantalla de registro, una pantalla de "contraseña olvidada", una pantalla de "restablecer contraseña para otro usuario" del administrador, etc.

Pero en cualquiera de esos casos, siempre estará vinculado a algún campo de entrada de texto. Así que ahí es donde debería estar acoplado.

Cree un componente React muy pequeño que consista únicamente en un campo de entrada y la lógica de validación asociada. Ingrese ese componente dentro de todos los formularios que podrían querer ingresar una contraseña.

Es esencialmente el mismo resultado que tener un servicio / fábrica para la lógica, pero lo está acoplando directamente a la entrada. Así que ahora nunca necesita decirle a esa función dónde buscar su entrada de validación, ya que está permanentemente unida.

Jake Roby
fuente
11
Lo que es una mala práctica es combinar lógica e interfaz de usuario. Para cambiar la lógica tendré que tocar el componente
Dennis Nerush
14
Reaccionar fundamentalmente desafía esa suposición que estás haciendo. Está en marcado contraste con la arquitectura tradicional MVC. Este video hace un trabajo bastante bueno al explicar por qué es eso (la sección relevante comienza alrededor de 2 minutos).
Jake Roby
8
¿Qué sucede si la misma lógica de validación también debe aplicarse a un elemento de área de texto? La lógica aún necesita ser extraída en un archivo compartido. No creo que haya ninguna equivalencia de la biblioteca de reacción. Angular Service son inyectables, y Angular Framework está construido sobre el patrón de diseño de inyección de dependencia, que permite las instancias de las dependencias administradas por Angular. Cuando se inyecta un servicio, generalmente hay un singleton en el alcance proporcionado, para tener el mismo servicio en React, se debe introducir una aplicación DI de terceros en la aplicación.
Downhillski
15
@gravityplanx Disfruto usando React. Este no es un patrón angular, este es un patrón de diseño de software. Me gusta mantener mi mente abierta mientras tomo prestadas cosas que me gustan de otras partes buenas.
Downhillski
1
Los módulos @MickeyPuri ES6 no son lo mismo que Inyección de dependencias.
Spock
12

También vine del área de Angular.js y los servicios y las fábricas en React.js son más simples.

Puede usar funciones o clases simples, estilo de devolución de llamada y eventos Mobx como yo :)

// Here we have Service class > dont forget that in JS class is Function
class HttpService {
  constructor() {
    this.data = "Hello data from HttpService";
    this.getData = this.getData.bind(this);
  }

  getData() {
    return this.data;
  }
}


// Making Instance of class > it's object now
const http = new HttpService();


// Here is React Class extended By React
class ReactApp extends React.Component {
  state = {
    data: ""
  };

  componentDidMount() {
    const data = http.getData();

    this.setState({
      data: data
    });
  }

  render() {
    return <div>{this.state.data}</div>;
  }
}

ReactDOM.render(<ReactApp />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  
  <div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

</body>
</html>

Aquí hay un ejemplo simple:

Juraj
fuente
React.js es una biblioteca de interfaz de usuario para representar y organizar los componentes de la interfaz de usuario. Cuando se trata de servicios que pueden ayudarnos a agregar funcionalidades adicionales, entonces deberíamos crear colecciones de funciones, objetos funcionales o clases. Encontré las clases muy útiles, pero sé que estoy jugando también con un estilo funcional que también se puede usar para crear ayudantes para agregar una funcionalidad ventajosa que está fuera del alcance de Reac.js.
Juraj
Acabo de implementar esto. La forma en que lo convertiste en una clase y lo exportaste es bastante elegante.
GavinBelson
10

La misma situación: después de haber realizado múltiples proyectos angulares y pasar a Reaccionar, no tener una manera simple de proporcionar servicios a través de DI parece una pieza faltante (dejando de lado los detalles del servicio).

Usando contexto y decoradores ES7 podemos acercarnos:

https://jaysoo.ca/2015/06/09/react-contexts-and-dependency-injection/

Parece que estos muchachos lo han llevado un paso más allá / en una dirección diferente:

http://blog.wolksoftware.com/dependency-injection-in-react-powered-inversifyjs

Todavía se siente como trabajar contra la corriente. Revisará esta respuesta dentro de 6 meses después de emprender un importante proyecto React.

EDITAR: Volver 6 meses después con algo más de experiencia React. Considere la naturaleza de la lógica:

  1. ¿Está vinculado (solo) a la interfaz de usuario? Moverlo a un componente (respuesta aceptada).
  2. ¿Está vinculado (solo) a la gestión estatal? Muévelo a un golpe .
  3. Atado a ambos? Mover a un archivo separado, consumir en componente a través de un selector y en thunks.

Algunos también buscan HOC para su reutilización, pero para mí lo anterior cubre casi todos los casos de uso. Además, considere escalar la administración del estado utilizando patos para mantener las preocupaciones separadas y centradas en la interfaz de usuario estatal.

corola
fuente
En mi humilde opinión creo que no es una forma sencilla de proporcionar servicios thru DI, mediante el uso del sistema Módulo ES6
Mickey Puri
1
@MickeyPuri, el módulo ES6 DI no incluiría la naturaleza jerárquica de Angular DI, es decir. padres (en DOM) instanciando y anulando servicios suministrados a componentes secundarios. El módulo DI de Imho ES6 se compara más cerca de los sistemas DI de back-end como Ninject y Structuremap, separados de la jerarquía de componentes DOM, en lugar de basarse en ellos. Pero me gustaría escuchar tu opinión al respecto.
corola
6

Yo también soy de Angular y estoy probando React, a partir de ahora, una forma recomendada (?) Parece estar usando Componentes de alto orden :

Un componente de orden superior (HOC) es una técnica avanzada en React para reutilizar la lógica del componente. Los HOC no son parte de la API React, per se. Son un patrón que emerge de la naturaleza compositiva de React.

Digamos que tiene inputy le textareagusta aplicar la misma lógica de validación:

const Input = (props) => (
  <input type="text"
    style={props.style}
    onChange={props.onChange} />
)
const TextArea = (props) => (
  <textarea rows="3"
    style={props.style}
    onChange={props.onChange} >
  </textarea>
)

Luego escriba un HOC que valide y estilice el componente envuelto:

function withValidator(WrappedComponent) {
  return class extends React.Component {
    constructor(props) {
      super(props)

      this.validateAndStyle = this.validateAndStyle.bind(this)
      this.state = {
        style: {}
      }
    }

    validateAndStyle(e) {
      const value = e.target.value
      const valid = value && value.length > 3 // shared logic here
      const style = valid ? {} : { border: '2px solid red' }
      console.log(value, valid)
      this.setState({
        style: style
      })
    }

    render() {
      return <WrappedComponent
        onChange={this.validateAndStyle}
        style={this.state.style}
        {...this.props} />
    }
  }
}

Ahora esos HOC comparten el mismo comportamiento de validación:

const InputWithValidator = withValidator(Input)
const TextAreaWithValidator = withValidator(TextArea)

render((
  <div>
    <InputWithValidator />
    <TextAreaWithValidator />
  </div>
), document.getElementById('root'));

Creé una demostración simple .

Editar : otra demostración está utilizando accesorios para pasar una serie de funciones para que pueda compartir la lógica compuesta por múltiples funciones de validación en HOCs como:

<InputWithValidator validators={[validator1,validator2]} />
<TextAreaWithValidator validators={[validator1,validator2]} />

Edit2 : React 16.8+ proporciona una nueva característica, Hook , otra buena forma de compartir lógica.

const Input = (props) => {
  const inputValidation = useInputValidation()

  return (
    <input type="text"
    {...inputValidation} />
  )
}

function useInputValidation() {
  const [value, setValue] = useState('')
  const [style, setStyle] = useState({})

  function handleChange(e) {
    const value = e.target.value
    setValue(value)
    const valid = value && value.length > 3 // shared logic here
    const style = valid ? {} : { border: '2px solid red' }
    console.log(value, valid)
    setStyle(style)
  }

  return {
    value,
    style,
    onChange: handleChange
  }
}

https://stackblitz.com/edit/react-shared-validation-logic-using-hook?file=index.js

Beto
fuente
Gracias. Realmente aprendí de esta solución. ¿Qué pasa si necesito tener más de un validador? Por ejemplo, además del validador de 3 letras, ¿qué pasa si quiero tener otro validador que se asegure de que no se ingresen números? ¿Podríamos componer validadores?
Youssef Sherif
1
@YoussefSherif Puede preparar múltiples funciones de validación y pasarlas como accesorios de HOC, vea mi edición para otra demostración.
bob
entonces HOC es básicamente un componente contenedor?
sensei
Sí, de React doc: "Tenga en cuenta que un HOC no modifica el componente de entrada, ni utiliza la herencia para copiar su comportamiento. Por el contrario, un HOC compone el componente original envolviéndolo en un componente contenedor. Un HOC es un puro funcionar con cero efectos secundarios ".
bob
1
El requisito era inyectar lógica, no veo por qué necesitamos un HOC para hacer esto. Si bien puede hacerlo con un HOC, se siente demasiado complicado. Mi comprensión de los HOC es cuando también hay algún estado adicional que debe agregarse y administrarse, es decir, no es pura lógica (que fue el caso aquí).
Mickey Puri
4

El servicio no se limita a Angular, incluso en Angular2 + ,

El servicio es solo una colección de funciones auxiliares ...

Y hay muchas formas de crearlos y reutilizarlos en la aplicación ...

1) Pueden ser todas las funciones separadas que se exportan desde un archivo js, ​​similar a la siguiente:

export const firstFunction = () => {
   return "firstFunction";
}

export const secondFunction = () => {
   return "secondFunction";
}
//etc

2) También podemos usar métodos de fábrica como, con la colección de funciones ... con ES6 puede ser una clase en lugar de un constructor de funciones:

class myService {

  constructor() {
    this._data = null;
  }

  setMyService(data) {
    this._data = data;
  }

  getMyService() {
    return this._data;
  }

}

En este caso necesita hacer una instancia con nueva clave ...

const myServiceInstance = new myService();

También en este caso, cada instancia tiene su propia vida, así que tenga cuidado si desea compartirla, en ese caso debe exportar solo la instancia que desee ...

3) Si su función y utilidades no se compartirán, incluso puede ponerlos en el componente Reaccionar, en este caso, igual que la función en su componente reaccionar ...

class Greeting extends React.Component {
  getName() {
    return "Alireza Dezfoolian";
  }

  render() {
    return <h1>Hello, {this.getName()}</h1>;
  }
}

4) Otra forma de manejar las cosas, podría ser usar Redux , es una tienda temporal para usted, por lo que si lo tiene en su aplicación React , puede ayudarlo con muchas funciones getter setter que usa ... Es como una gran tienda que hacen un seguimiento de sus estados y pueden compartirlo entre sus componentes, por lo que pueden deshacerse de muchas molestias por las cosas de getter setter que usamos en los servicios ...

Siempre es bueno hacer un código DRY y no repetir lo que se debe usar para hacer que el código sea reutilizable y legible, pero no intente seguir formas angulares en la aplicación React , como se menciona en el elemento 4, usar Redux puede reducir su necesidad de servicios y limita su uso para algunas funciones auxiliares reutilizables como el elemento 1 ...

Alireza
fuente
Claro, puedes encontrarlo en mi sitio web personal, que es un enlace desde mi página de perfil ...
Alireza
"No sigas las formas angulares en React" .. ejem Angular promueve el uso de Redux y transmite la tienda a los componentes de presentación usando Observables y gestión de estado similar a Redux como RxJS / Store. .. te refieres a AngularJS? Porque eso es otra cosa
Spock
1

Estoy en la misma bota como tú. En el caso que mencione, implementaría el componente UI de validación de entrada como un componente React.

Estoy de acuerdo en que la implementación de la lógica de validación en sí misma no debe (debe) estar acoplada. Por lo tanto, lo pondría en un módulo JS separado.

Es decir, para la lógica que no debe acoplarse, use un módulo / clase JS en un archivo separado, y use require / import para desacoplar el componente del "servicio".

Esto permite la inyección de dependencia y la prueba unitaria de los dos de forma independiente.

sibidiba
fuente
1

o puede inyectar la herencia de clase "http" en React Component

a través de objetos de utilería.

  1. actualización:

    ReactDOM.render(<ReactApp data={app} />, document.getElementById('root'));
  2. Simplemente edite React Component ReactApp de esta manera:

    class ReactApp extends React.Component {
    
    state = {
    
        data: ''
    
    }
    
        render(){
    
        return (
            <div>
            {this.props.data.getData()}      
            </div>
    
        )
        }
    }
Juraj
fuente
0

Bueno, el patrón más utilizado para la lógica reutilizable que he encontrado es escribir un gancho o crear un archivo de utilidades. Depende de lo que quieras lograr.

hooks/useForm.js

Por ejemplo, si desea validar datos de formulario, crearía un enlace personalizado llamado useForm.js y le proporcionaría datos de formulario y, a cambio, me devolvería un objeto que contiene dos cosas:

Object: {
    value,
    error,
}

Definitivamente puedes devolver más cosas a medida que progresas.

utils/URL.js

Otro ejemplo sería como si quisiera extraer alguna información de una URL y luego crearía un archivo de utilidades que contenga una función e importarla donde sea necesario:

 export function getURLParam(p) {
...
}
Muhammad Shahryar
fuente