web-dev-qa-db-fra.com

Avoir des services dans l'application React

Je viens du monde angular où je pourrais extraire la logique d’un service/d’une usine et la consommer dans mes contrôleurs.

J'essaie de comprendre comment obtenir la même chose dans une application React.

Disons que j'ai un composant qui valide la saisie du mot de passe de l'utilisateur (sa force). C'est une logique assez complexe donc je ne veux pas l'écrire dans le composant lui-même.

Où devrais-je écrire cette logique? Dans un magasin si j'utilise du flux? Ou y a-t-il une meilleure option?

114
Dennis Nerush

La première réponse ne reflète pas le paradigme actuel Container vs Presenter .

Si vous devez faire quelque chose, comme valider un mot de passe, vous aurez probablement une fonction qui le fait. Vous seriez en train de passer cette fonction à votre vue réutilisable en tant qu'accessoire.

Les conteneurs

La bonne façon de le faire est donc d'écrire un ValidatorContainer, qui aura cette fonction comme propriété, et d'y insérer le formulaire, en transmettant les bons accessoires à l'enfant. En ce qui concerne votre vue, votre conteneur de validation englobe votre vue et celle-ci utilise la logique des conteneurs.

La validation peut s'effectuer dans les propriétés du conteneur, mais si vous utilisez un validateur tiers ou tout autre service de validation, vous pouvez utiliser le service en tant que propriété du composant conteneur et l'utiliser dans les méthodes du conteneur. Je l'ai fait pour des composants reposants et cela fonctionne très bien.

Fournisseurs

Si un peu plus de configuration est nécessaire, vous pouvez utiliser un modèle fournisseur/consommateur. Un fournisseur est un composant de haut niveau qui englobe quelque part et sous l'objet d'application supérieur (celui que vous montez) et fournit une partie de lui-même, ou une propriété configurée dans la couche supérieure, à l'API de contexte. J'ai ensuite défini mes éléments de conteneur pour utiliser le contexte.

Les relations de contexte parent/enfant ne doivent pas nécessairement être proches les unes des autres, il suffit que l’enfant soit descendu d’une manière ou d’une autre. Les magasins Redux et le routeur React fonctionnent de cette manière. Je l'ai utilisé pour fournir un contexte reposant à la racine de mes conteneurs de repos (si je ne fournis pas le mien).

(Remarque: l'API de contexte est marquée expérimentale dans la documentation, mais je ne pense pas qu'elle l'est plus, compte tenu de ce qui l'utilise).

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

Une autre manière que je n'ai pas encore essayée, mais que j'ai utilisée, consiste à utiliser un middleware avec Redux. Vous définissez votre objet de service en dehors de l'application, ou au moins, plus haut que le magasin redux. Lors de la création du magasin, vous injectez le service dans le middleware, lequel gère toutes les actions qui affectent le service.

De cette manière, je pouvais injecter mon objet reposful.js dans le middleware et remplacer mes méthodes de conteneur par des actions indépendantes. J'aurais encore besoin d'un composant conteneur pour fournir les actions à la couche de vue formulaire, mais connect () et mapDispatchToProps m'ont couvert ici.

La nouvelle v4 react-router-redux utilise cette méthode pour affecter l’état de l’historique, par exemple.

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

Le problème devient extrêmement simple lorsque vous réalisez qu'un service Angular n'est qu'un objet qui fournit un ensemble de méthodes indépendantes du contexte. C'est juste le mécanisme AngularDI qui le rend plus compliqué. La DI est utile car elle prend en charge la création et la maintenance d'instances, mais vous n'en avez pas vraiment besoin.

Considérez une bibliothèque AJAX populaire appelée axios (dont vous avez probablement entendu parler):

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

Ne se comporte-t-il pas comme un service? Il fournit un ensemble de méthodes responsables d'une logique spécifique et est indépendant du code principal.

Votre exemple de cas concernait la création d'un ensemble isolé de méthodes pour valider vos entrées (par exemple, vérifier la force du mot de passe). Certains ont suggéré de mettre ces méthodes à l'intérieur des composants, ce qui pour moi est clairement un anti-modèle. Que se passe-t-il si la validation implique de passer et de traiter des appels backend XHR ou d'effectuer des calculs complexes? Souhaitez-vous mélanger cette logique avec des gestionnaires de clics de souris et d'autres éléments spécifiques à l'interface utilisateur? Absurdité. La même chose avec l'approche conteneur/HOC. Envelopper votre composant juste pour ajouter une méthode qui vérifiera si la valeur contient un chiffre? Allons.

Je créerais simplement un nouveau fichier nommé "ValidationService.js" et l'organiserais comme suit:

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

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

export default ValidationService;

Puis dans votre composant:

import ValidationService from "./services/ValidationService.js";

...

//inside the component
yourInputChangeHandler(event) {

    if(!ValidationService.firstValidationMethod(event.target.value) {
        //show a validation warning
        return false;
    }
    //proceed
}

Utilisez ce service depuis n'importe où. Si les règles de validation changent, vous devez vous concentrer uniquement sur le fichier ValidationService.js.

Vous aurez peut-être besoin d'un service plus complexe, qui dépend d'autres services. Dans ce cas, votre fichier de service peut renvoyer un constructeur de classe au lieu d'un objet statique afin que vous puissiez créer vous-même une instance de l'objet dans le composant. Vous pouvez également envisager de mettre en œuvre un singleton simple pour vous assurer qu'il n'y a toujours qu'une seule instance de l'objet de service utilisée dans toute l'application.

57
Wojciech Majerski

Gardez à l'esprit que le but de React est de mieux coupler des éléments qui devraient être couplés logiquement. Si vous concevez une méthode compliquée de "validation du mot de passe", où doit-elle être couplée?

Eh bien, vous allez avoir besoin de l'utiliser à chaque fois que l'utilisateur doit saisir un nouveau mot de passe. Cela pourrait être sur l'écran d'enregistrement, un écran "mot de passe oublié", un écran "réinitialiser le mot de passe pour un autre utilisateur" d'administrateur, etc.

Mais dans tous les cas, il sera toujours lié à un champ de saisie de texte. C'est donc là que cela devrait être couplé.

Créez un très petit composant React composé uniquement d'un champ d'entrée et de la logique de validation associée. Entrez ce composant dans tous les formulaires susceptibles de vouloir entrer un mot de passe.

C'est essentiellement le même résultat que d'avoir un service/une usine pour la logique, mais vous le couplez directement à l'entrée. Vous n’avez donc plus besoin de dire à cette fonction où chercher son entrée de validation, car elle est liée de manière permanente.

29
Jake Roby

J'avais besoin d'une logique de formatage partagée entre plusieurs composants et, en tant que développeur Angular, naturellement orientée naturellement vers un service.

J'ai partagé la logique en la mettant dans un fichier séparé

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

module.exports = {
    format: format
};

puis importé sous forme de module

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

//then in component

    render() {

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

Je viens aussi de la région d’Angular.js et les services et usines de React.js sont plus simples.

Vous pouvez utiliser des fonctions ou des classes simples, le style de rappel et l'événement Mobx comme moi :)

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

Voici un exemple simple:

9
Juraj

Même situation: après avoir effectué plusieurs Angular projets et migré vers React, ne pas avoir un moyen simple de fournir des services par le biais de DI semble être une pièce manquante (à part les détails du service).

En utilisant le décor et le décorateur ES7, nous pouvons nous en approcher:

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

On dirait que ces gars ont fait un pas de plus/dans une direction différente:

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

Toujours envie de travailler à contre-courant. Reviendrons sur cette réponse dans 6 mois après avoir entrepris un projet majeur React.

EDIT: De retour 6 mois plus tard avec un peu plus d'expérience React. Considérez la nature de la logique:

  1. Est-il lié (uniquement) à l'interface utilisateur? Déplacez-le dans un composant (réponse acceptée).
  2. Est-il lié (uniquement) à la gestion des états? Déplacez-le dans un thunk .
  3. Attaché aux deux? Déplacer vers un fichier séparé, consommer en composant via un sélecteur et en thunks.

Certains atteignent également HOC pour être réutilisés, mais pour moi, ce qui précède couvre presque tous les cas d'utilisation. En outre, envisagez de mettre à l’échelle la gestion de l’état à l’aide de canards , afin de séparer les problèmes et d’indiquer l’état centré sur l’UI.

9
corolla

Je viens de Angular aussi et de l'essai de React, une méthode recommandée (?) Semble utiliser High-Order Components :

Un composant d'ordre supérieur (HOC) est une technique avancée utilisée dans React pour la réutilisation de la logique de composant. Les HOC ne font pas partie de l'API React _ en soi. C’est un schéma qui émerge de la nature compositionnelle de React.

Supposons que vous avez input et textarea et aimez appliquer la même logique de validation:

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

Ensuite, écrivez un HOC qui valide et classe le composant encapsulé:

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

Maintenant, ces HOC partagent le même comportement de validation:

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

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

J'ai créé un simple démo .

Edit : Un autre démo utilise des accessoires pour transmettre un tableau de fonctions afin que vous puissiez partager une logique composée de plusieurs fonctions de validation à travers HOCs like:

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

Edit2 : React 16.8+ fournit une nouvelle fonctionnalité, Hook , un autre moyen intéressant de partager la logique.

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

4
bob

Le service n'est pas limité à Angular, même dans Angular2 +,

Le service n'est qu'une collection de fonctions d'assistance ...

Et il existe de nombreuses façons de les créer et de les réutiliser dans l'application ...

1) Ils peuvent être tous des fonctions séparées qui sont exportées à partir d'un fichier js, comme ci-dessous:

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

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

2) Nous pouvons également utiliser la méthode factory comme, avec une collection de fonctions ... avec ES6 il peut s'agir d'une classe plutôt que d'un constructeur de fonction:

class myService {

  constructor() {
    this._data = null;
  }

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

  getMyService() {
    return this._data;
  }

}

Dans ce cas, vous devez créer une instance avec une nouvelle clé ...

const myServiceInstance = new myService();

De plus, dans ce cas, chaque instance a sa propre vie, aussi soyez prudent si vous souhaitez la partager, dans ce cas, vous ne devez exporter que l'instance souhaitée ...

) Si votre fonction et vos utilitaires ne sont pas partagés, vous pouvez même les mettre dans le composant React, dans ce cas, tout comme la fonction dans votre composant réag ...

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

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

4) Une autre façon de gérer les choses pourrait être d'utiliser Redux, c'est un magasin temporaire pour vous, donc si vous l'avez dans votre application React =, il peut vous aider avec beaucoup de fonctions du getter setter que vous utilisez ... C’est comme un grand magasin qui garde une trace de vos états et peut le partager entre vos composants, vous permettant ainsi de vous débarrasser de nombreuses difficultés. getter setter, nous utilisons dans les services ...

Il est toujours bon de faire code DRY et de ne pas répéter ce qui doit être utilisé pour rendre le code réutilisable et lisible, mais n'essayez pas de suivre Angular manières dans React app, comme indiqué à la rubrique 4, l’utilisation de Redux peut réduire vos besoins en services et vous limiter à leur utilisation pour certaines fonctions auxiliaires réutilisables telles que la rubrique 1 ...

2
Alireza

ou vous pouvez injecter l'héritage de classe "http" dans React Component

via l'objet props.

  1. mise à jour :

    ReactDOM.render(<ReactApp data={app} />, document.getElementById('root'));
    
  2. Éditez simplement React Component ReactApp comme ceci:

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

Je suis dans le même coffre que toi. Dans le cas que vous mentionnez, je mettrais en œuvre le composant d'interface utilisateur de validation d'entrée en tant que composant React.

Je conviens que la mise en œuvre de la logique de validation elle-même ne doit (doit) pas être couplée. Par conséquent, je le mettrais dans un module JS séparé.

Autrement dit, pour une logique qui ne devrait pas être couplée, utilisez un module/une classe JS dans un fichier séparé et utilisez require/import pour dissocier le composant du "service".

Cela permet l'injection de dépendance et le test unitaire des deux indépendamment.

1
sibidiba