Avere servizi nell'applicazione React


177

Vengo dal mondo angolare dove ho potuto estrarre la logica in un servizio / fabbrica e consumarli nei miei controller.

Sto cercando di capire come posso ottenere lo stesso in un'applicazione React.

Diciamo che ho un componente che convalida l'immissione della password dell'utente (è la forza). La sua logica è piuttosto complessa, quindi non voglio scriverlo nel componente stesso.

Dove dovrei scrivere questa logica? In un negozio se sto usando il flusso? O c'è un'opzione migliore?


Puoi usare un pacchetto e vedere come lo stanno facendo - npmjs.com/package/react-password-strength-meter
James111

11
La forza della password è solo un esempio. Sto cercando una best practice più generale
Dennis Nerush

Potrebbe essere necessario farlo sul lato server?
James111

2
No. Solo logica lato client che non dovrebbe essere direttamente nel componente. Il controllo della sicurezza della password è solo un esempio
Dennis Nerush

4
Se si dispone di molte di queste funzioni, è possibile memorizzarle in un file di supporto e richiederlo nel proprio file componente per l'utilizzo. Se è una singola funzione che è rilevante esclusivamente per quel componente, probabilmente dovrebbe vivere lì, indipendentemente dalla complessità.
Jesse Kernaghan,

Risposte:


61

La prima risposta non riflette l'attuale paradigma Container vs Presenter .

Se devi fare qualcosa, come convalidare una password, probabilmente avrai una funzione che lo fa. Passeresti quella funzione alla tua vista riutilizzabile come oggetto di scena.

contenitori

Quindi, il modo corretto per farlo è scrivere un ValidatorContainer, che avrà quella funzione come proprietà, e avvolgere il modulo in esso, passando i puntelli giusti al bambino. Quando si tratta della vista, il contenitore del validatore esegue il wrapping della vista e la vista utilizza la logica dei contenitori.

La convalida può essere eseguita tutte nelle proprietà del contenitore, ma se si utilizza un validatore di terze parti o qualsiasi servizio di convalida semplice, è possibile utilizzare il servizio come proprietà del componente contenitore e utilizzarlo nei metodi del contenitore. L'ho fatto per componenti riposanti e funziona molto bene.

provider

Se è necessaria un po 'più di configurazione, è possibile utilizzare un modello Provider / Consumer. Un provider è un componente di alto livello che avvolge da qualche parte vicino e sotto l'oggetto principale dell'applicazione (quello che monti) e fornisce una parte di se stesso, o una proprietà configurata nel livello superiore, all'API di contesto. Ho quindi impostato i miei elementi contenitore per utilizzare il contesto.

Le relazioni di contesto genitore / figlio non devono essere vicine l'una all'altra, solo il bambino deve essere disceso in qualche modo. I negozi Redux e la funzione React Router funzionano in questo modo. L'ho usato per fornire un contesto riposante di root per i miei contenitori di riposo (se non fornisco il mio).

(nota: l'API di contesto è contrassegnata come sperimentale nei documenti, ma non credo che lo sia più, considerando cosa lo sta 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

Un altro modo che non ho provato, ma visto usato, è usare il middleware insieme a Redux. Definisci il tuo oggetto di servizio al di fuori dell'applicazione, o almeno, più in alto rispetto al redux store. Durante la creazione del negozio, il servizio viene iniettato nel middleware e il middleware gestisce tutte le azioni che influiscono sul servizio.

In questo modo, potrei iniettare il mio oggetto restful.js nel middleware e sostituire i metodi del mio contenitore con azioni indipendenti. Avrei ancora bisogno di un componente contenitore per fornire le azioni al livello vista modulo, ma connect () e mapDispatchToProps mi hanno coperto lì.

Il nuovo reagente-router-redux v4 utilizza questo metodo per influire, ad esempio, sullo stato della cronologia.

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


ottima risposta amico, mi hai impedito di fare cose senza cervello 8) KUDOS !!
csomakk,

qual è l'utilizzo per esempio container?
sensei,

Non lo sto sostenendo, ma se si volesse percorrere il percorso del localizzatore di servizi (qualcosa di simile a Angular), è possibile aggiungere una sorta di provider "iniettore / contenitore" da cui si risolvono i servizi (dopo averli registrati in precedenza).
eddiewould

I ganci React vengono in soccorso. Con Hooks puoi scrivere logiche riutilizzabili senza scrivere una classe. reazionejs.org/docs/…
Raja Malik il

102

Il problema diventa estremamente semplice quando ti rendi conto che un servizio angolare è solo un oggetto che offre una serie di metodi indipendenti dal contesto. È solo il meccanismo angolare DI che lo rende più complicato. Il DI è utile in quanto si occupa di creare e mantenere istanze per te ma non ne hai davvero bisogno.

Considera una popolare libreria AJAX di nome axios (di cui probabilmente avrai sentito parlare):

import axios from "axios";
axios.post(...);

Non si comporta come un servizio? Fornisce una serie di metodi responsabili di alcune logiche specifiche ed è indipendente dal codice principale.

Il tuo esempio è stato quello di creare un insieme isolato di metodi per convalidare i tuoi input (ad es. Controllo della sicurezza della password). Alcuni hanno suggerito di inserire questi metodi all'interno dei componenti che per me è chiaramente un anti-pattern. Cosa succede se la convalida comporta l'esecuzione e l'elaborazione di chiamate back-end XHR o l'esecuzione di calcoli complessi? Mescoleresti questa logica con i gestori di clic del mouse e altri elementi specifici dell'interfaccia utente? Senza senso. Lo stesso con l'approccio container / HOC. Avvolgere il componente solo per l'aggiunta di un metodo che controllerà se il valore contiene una cifra? Dai.

Vorrei solo creare un nuovo file chiamato dire 'ValidationService.js' e organizzarlo come segue:

const ValidationService = {
    firstValidationMethod: function(value) {
        //inspect the value
    },

    secondValidationMethod: function(value) {
        //inspect the value
    }
};

export default ValidationService;

Quindi nel tuo 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
}

Usa questo servizio ovunque tu voglia. Se le regole di convalida cambiano, è necessario concentrarsi solo sul file ValidationService.js.

Potrebbe essere necessario un servizio più complicato che dipende da altri servizi. In questo caso il tuo file di servizio potrebbe restituire un costruttore di classi anziché un oggetto statico in modo da poter creare un'istanza dell'oggetto da solo nel componente. Puoi anche prendere in considerazione l'implementazione di un singolo singleton per assicurarti che esista sempre solo un'istanza dell'oggetto di servizio in uso nell'intera applicazione.


3
Questo è il modo in cui lo farei anch'io. Sono abbastanza sorpreso dal fatto che questa risposta abbia così pochi voti, dato che questa sembra essere la strada con il minor attrito. Se il tuo servizio dipende da altri servizi, quindi, sarebbe importare quegli altri servizi tramite i loro moduli. Inoltre i moduli sono, per definizione, singoli, quindi in realtà non c'è altro lavoro necessario per "implementarlo come un semplice singleton" - ottieni questo comportamento gratuitamente :)
Mickey Puri

6
+1: bella risposta se stai utilizzando solo servizi che forniscono funzioni. Tuttavia , il servizio di Angular è costituito da classi definite una volta, fornendo così più funzionalità rispetto alla semplice fornitura di funzioni. Ad esempio, è possibile memorizzare oggetti nella cache come parametro della classe di servizio.
Nino Filiu,

6
Questa dovrebbe essere la vera risposta, e non la risposta troppo complicata sopra
user1807334

1
Questa è una buona risposta, tranne che non è "reattiva". Il DOM non si aggiornerà sulle modifiche variabili all'interno del servizio.
Defacto,

9
Che dire dell'iniezione di dipendenza però? Il servizio è impossibile da deridere nel componente a meno che non lo inietti in qualche modo. Forse avere un oggetto globale "container" di livello superiore che ha ogni servizio come campo potrebbe aggirare questo. Quindi, nei test, è possibile ignorare i campi Contenitore con simulazioni per i servizi che si desidera deridere.
menehune23,

34

Avevo bisogno di un po 'di logica di formattazione da condividere tra più componenti e, in qualità di sviluppatore angolare, mi sono naturalmente orientato verso un servizio.

Ho condiviso la logica inserendola in un file separato

function format(input) {
    //convert input to output
    return output;
}

module.exports = {
    format: format
};

e poi lo ha importato come modulo

import formatter from '../services/formatter.service';

//then in component

    render() {

        return formatter.format(this.props.data);
    }

8
Questa è una buona idea, come anche menzionato nel documento di React: reazionijs.org/docs/composition-vs-inheritance.html Se si desidera riutilizzare la funzionalità non dell'interfaccia utente tra i componenti, si consiglia di estrarla in un modulo JavaScript separato. I componenti possono importarlo e utilizzare tale funzione, oggetto o classe, senza estenderlo.
user3426603,

Questa è in realtà l'unica risposta qui sensata.
Artem Novikov,

33

Tieni presente che lo scopo di React è quello di accoppiare meglio le cose che logicamente dovrebbero essere accoppiate. Se stai progettando un metodo complicato di "convalida password", dove dovrebbe essere accoppiato?

Bene, dovrai utilizzarlo ogni volta che l'utente deve inserire una nuova password. Potrebbe essere nella schermata di registrazione, nella schermata "password dimenticata", nella schermata "reimposta password amministratore per un altro utente", ecc.

Ma in ognuno di questi casi, sarà sempre legato ad un campo di input di testo. Ecco dove dovrebbe essere accoppiato.

Crea un componente React molto piccolo costituito esclusivamente da un campo di input e dalla logica di convalida associata. Immettere quel componente in tutti i moduli che potrebbero voler inserire una password.

È essenzialmente lo stesso risultato di avere un servizio / fabbrica per la logica, ma lo stai accoppiando direttamente all'input. Quindi ora non hai mai bisogno di dire a quella funzione dove cercare il suo input di validazione, poiché è permanentemente legato insieme.


11
Che cosa è una cattiva pratica accoppiare la logica e l'interfaccia utente. Per cambiare la logica dovrò toccare il componente
Dennis Nerush

14
Reagire fondamentalmente sfida quell'ipotesi che stai facendo. È in netto contrasto con la tradizionale architettura MVC. Questo video fa un ottimo lavoro nel spiegare perché (la sezione pertinente inizia tra circa 2 minuti).
Jake Roby

8
Cosa succede se è necessario applicare la stessa logica di convalida a un elemento dell'area di testo? La logica deve ancora essere estratta in un file condiviso. Non credo che ci sia equivalenza dalla libreria di reazioni. Il servizio angolare è iniettabile e il framework angolare è basato sul modello di progettazione dell'iniezione di dipendenze, che consente le istanze delle dipendenze gestite da Angular. Quando viene iniettato un servizio, di solito esiste un singleton nell'ambito fornito, per avere lo stesso servizio in React, è necessario introdurre un'applicazione DI lib di terze parti nell'applicazione.
Downhillski,

15
@gravityplanx Mi piace usare React. Questo non è un modello angolare, questo è un modello di progettazione software. Mi piace tenere la mente aperta mentre prendo in prestito cose che mi piacciono da altre parti buone.
Downhillski,

1
I moduli @MickeyPuri ES6 non sono gli stessi dell'iniezione di dipendenza.
Spock,

12

Vengo anche dall'area di Angular.js e i servizi e le fabbriche in React.js sono più semplici.

Puoi usare semplici funzioni o classi, stile di callback ed eventi Mobx come me :)

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

Ecco un semplice esempio:


React.js è una libreria UI per il rendering e l'organizzazione dei componenti dell'interfaccia utente. Quando si tratta di servizi che possono aiutarci ad aggiungere funzionalità aggiuntive, dovremmo creare raccolte di funzioni, oggetti funzionali o classi. Ho trovato le lezioni molto utili, ma so che sto giocando anche con uno stile funzionale che può essere utilizzato anche per creare un aiuto per l'aggiunta di funzionalità avvantaggiate che non rientrano nell'ambito di Reac.js.
Juraj,

Ho appena implementato questo. Il modo in cui l'hai resa una classe ed esportata è piuttosto elegante.
GavinBelson,

10

Stessa situazione: dopo aver realizzato più progetti angolari e passare a React, non avere un modo semplice di fornire servizi tramite DI sembra un pezzo mancante (a parte i dettagli del servizio).

Usando i decoratori di contesto e ES7 possiamo avvicinarci:

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

Sembra che questi ragazzi abbiano fatto un passo avanti / in una direzione diversa:

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

Sembra ancora di lavorare contro il grano. Rivisiteremo questa risposta tra 6 mesi dopo aver intrapreso un importante progetto React.

EDIT: Torna 6 mesi dopo con un po 'più di esperienza React. Considera la natura della logica:

  1. È legato (solo) all'interfaccia utente? Spostalo in un componente (risposta accettata).
  2. È legato (solo) alla gestione statale? Spostalo in un thunk .
  3. Legato ad entrambi? Passa a un file separato, consumalo nel componente attraverso un selettore e in thunk.

Alcuni richiedono anche HOC per il riutilizzo, ma per me quanto sopra copre quasi tutti i casi d'uso. Inoltre, considera di ridimensionare la gestione dello stato usando le anatre per mantenere le preoccupazioni separate e dichiarare incentrato sull'interfaccia utente.


Imho Penso che ci sia un modo semplice per fornire servizi tramite DI, utilizzando il sistema del modulo ES6
Mickey Puri

1
@MickeyPuri, il modulo ES6 DI non include la natura gerarchica di DI angolare, ad es. genitori (in DOM) servizi di istanza e sostituzione forniti ai componenti figlio. Il modulo DI ES6 ESho si confronta con i sistemi DI back-end come Ninject e Structuremap, distinguendosi, anziché basandosi sulla gerarchia dei componenti DOM. Ma mi piacerebbe sentire i tuoi pensieri al riguardo.
corolla,

6

Anch'io sono angolare e sto provando React, a partire da ora, un modo raccomandato (?) Sembra usare componenti di alto ordine :

Un componente di ordine superiore (HOC) è una tecnica avanzata in React per il riutilizzo della logica dei componenti. Gli HOC non fanno parte dell'API React, di per sé. Sono uno schema che emerge dalla natura compositiva di React.

Supponiamo che tu abbia inpute textareadesideri applicare la stessa logica di convalida:

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>
)

Quindi scrivere un HOC che convalidi e modifichi il componente con wrapping:

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

Ora questi HOC condividono lo stesso comportamento di convalida:

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

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

Ho creato un semplice demo .

Modifica : un'altra demo utilizza i puntelli per passare una serie di funzioni in modo da poter condividere la logica composta da più funzioni di convalida tra HOCs come:

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

Edit2 : React 16.8+ offre una nuova funzionalità, Hook , un altro bel modo di condividere la logica.

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


Grazie. Ho davvero imparato da questa soluzione. E se avessi bisogno di più di un validatore. Ad esempio, oltre al validatore a 3 lettere, cosa succede se voglio avere un altro validatore che assicuri che non vengano inseriti numeri. Potremmo comporre validatori?
Youssef Sherif,

1
@YoussefSherif Puoi preparare più funzioni di validazione e passarle come oggetti di scena HOC, vedi la mia modifica per un'altra demo.
bob

quindi HOC è sostanzialmente un componente contenitore?
sensei,

Sì, dal documento React: "Nota che un HOC non modifica il componente di input, né usa l'ereditarietà per copiarne il comportamento. Piuttosto, un HOC compone il componente originale avvolgendolo in un componente contenitore. Un HOC è un puro funzione con zero effetti collaterali. "
bob

1
Il requisito era di iniettare la logica, non vedo perché abbiamo bisogno di un HOC per farlo. Mentre puoi farlo con un HOC, sembra troppo complicato. La mia comprensione degli HOC è quando c'è anche qualche stato aggiuntivo che deve essere aggiunto e gestito, vale a dire non pura logica (come è avvenuto qui).
Mickey Puri,

4

Il servizio non è limitato a Angular, anche in Angular2 + ,

Il servizio è solo una raccolta di funzioni di supporto ...

E ci sono molti modi per crearli e riutilizzarli in tutta l'applicazione ...

1) Possono essere tutte funzioni separate che vengono esportate da un file js, simile come di seguito:

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

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

2) Possiamo anche usare il metodo factory come, con la raccolta di funzioni ... con ES6 può essere una classe piuttosto che un costruttore di funzioni:

class myService {

  constructor() {
    this._data = null;
  }

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

  getMyService() {
    return this._data;
  }

}

In questo caso è necessario creare un'istanza con una nuova chiave ...

const myServiceInstance = new myService();

Anche in questo caso, ogni istanza ha la sua vita, quindi fai attenzione se vuoi condividerla, in quel caso dovresti esportare solo l'istanza che desideri ...

3) Se la tua funzione e utilità non saranno condivise, puoi anche metterle nel componente React, in questo caso, proprio come la funzione nel tuo componente reagire ...

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

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

4) Un altro modo in cui puoi gestire le cose, potrebbe essere usare Redux , è un archivio temporaneo per te, quindi se lo hai nell'applicazione React , può aiutarti con molte funzioni di setter getter che usi ... È come un grande negozio che tengono traccia dei tuoi stati e possono condividerli tra i tuoi componenti, quindi puoi sbarazzarti di molti dolori per le cose getter setter che usiamo nei servizi ...

È sempre bene fare un codice DRY e non ripetere ciò che deve essere usato per rendere il codice riutilizzabile e leggibile, ma non provare a seguire i modi angolari nell'app React , come menzionato al punto 4, l'uso di Redux può ridurre la necessità di servizi e si limita a usarli per alcune funzioni di supporto riutilizzabili come l'articolo 1 ...


Certo, puoi trovarlo sul mio sito personale che è link dalla mia pagina del profilo ...
Alireza,

"Non seguire le vie angolari in React" .. ahem Angular promuove l'utilizzo di Redux e trasmette lo store ai componenti di presentazione utilizzando Observables e una gestione dello stato simile a Redux come RxJS / Store. .. intendevi AngularJS? Perché è un'altra cosa
Spock,

1

Sono nello stesso stivale come te. Nel caso in cui lei menziona, implementerei il componente UI di convalida dell'input come componente React.

Sono d'accordo che l'implementazione della logica di validazione stessa non debba (non debba) essere accoppiata. Pertanto lo inserisco in un modulo JS separato.

In altre parole, per la logica che non deve essere accoppiata, utilizzare un modulo / classe JS in un file separato e utilizzare un comando / importazione per disaccoppiare il componente dal "servizio".

Ciò consente l'iniezione di dipendenza e il test unitario dei due in modo indipendente.


1

oppure puoi iniettare l'eredità della classe "http" in React Component

tramite oggetto oggetti di scena.

  1. aggiornare :

    ReactDOM.render(<ReactApp data={app} />, document.getElementById('root'));
  2. Modifica semplicemente React Component ReactApp in questo modo:

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

0

Bene, il modello più usato per la logica riutilizzabile che ho incontrato è scrivere un hook o creare un file utils. Dipende da cosa vuoi realizzare.

hooks/useForm.js

Come se volessi convalidare i dati del modulo, creerei un hook personalizzato chiamato useForm.js e gli fornirò i dati del modulo e in cambio mi restituirebbe un oggetto contenente due cose:

Object: {
    value,
    error,
}

Puoi sicuramente restituire più cose da esso man mano che avanzi.

utils/URL.js

Un altro esempio potrebbe essere quello di voler estrarre alcune informazioni da un URL, quindi creerei un file utils contenente una funzione e importarlo dove necessario:

 export function getURLParam(p) {
...
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.