Tendo serviços no aplicativo React

177

Eu estou vindo do mundo angular onde eu poderia extrair lógica para um serviço / fábrica e consumi-la em meus controladores.

Estou tentando entender como conseguir o mesmo em um aplicativo React.

Digamos que eu tenho um componente que valida a entrada de senha do usuário (é forte). Sua lógica é bastante complexa, portanto, não quero escrevê-la no componente em si.

Onde devo escrever essa lógica? Em uma loja se estou usando fluxo? Ou existe uma opção melhor?

Dennis Nerush
fonte
Você poderia usar um pacote e ver como eles estão fazendo isso - npmjs.com/package/react-password-strength-meter
James111
11
A força da senha é apenas um exemplo. Eu estou procurando uma melhor prática mais geral
Dennis Nerush
Você pode ter que fazer isso do lado do servidor?
James111
2
Não. Somente a lógica do lado do cliente que não deve estar diretamente no componente. O verificador de força da senha é apenas um exemplo
Dennis Nerush
4
Se você tiver muitas dessas funções, poderá armazená-las em um arquivo auxiliar e apenas solicitá-las no seu arquivo de componente para uso. Se é uma função única que é relevante apenas para esse componente, ela provavelmente deve estar lá, não importa a complexidade.
Jesse Kernaghan

Respostas:

61

A primeira resposta não reflete o atual paradigma Container vs Presenter .

Se você precisar fazer algo, como validar uma senha, provavelmente terá uma função que faz isso. Você passaria essa função para sua visão reutilizável como suporte.

Recipientes

Portanto, a maneira correta de fazer isso é escrever um ValidatorContainer, que terá essa função como uma propriedade, e agrupar o formulário nele, passando os objetos certos para o filho. Quando se trata de sua visualização, o contêiner do validador agrupa a visualização e a visualização consome a lógica dos contêineres.

A validação pode ser feita nas propriedades do contêiner, mas se você estiver usando um validador de terceiros ou qualquer serviço de validação simples, poderá usar o serviço como uma propriedade do componente do contêiner e usá-lo nos métodos do contêiner. Eu fiz isso por componentes repousantes e funciona muito bem.

Fornecedores

Se for necessário um pouco mais de configuração, você pode usar um modelo de Provedor / Consumidor. Um provedor é um componente de alto nível que envolve em algum lugar perto e embaixo do objeto de aplicativo principal (aquele que você monta) e fornece uma parte de si mesmo ou uma propriedade configurada na camada superior à API de contexto. Em seguida, defino meus elementos de contêiner para consumir o contexto.

As relações de contexto pai / filho não precisam estar próximas uma da outra, apenas a criança precisa ser descendente de alguma forma. O Redux armazena e o React Router funcionam dessa maneira. Usei-o para fornecer um contexto de repouso para os meus contêineres de descanso (se eu não fornecer o meu).

(observação: a API de contexto é marcada como experimental nos documentos, mas acho que não é mais, considerando o que está sendo usado).

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

Uma outra maneira que eu não tentei, mas vi usada, é usar o middleware em conjunto com o Redux. Você define seu objeto de serviço fora do aplicativo, ou pelo menos, superior ao repositório redux. Durante a criação da loja, você injeta o serviço no middleware e o middleware lida com as ações que afetam o serviço.

Dessa maneira, eu poderia injetar meu objeto restful.js no middleware e substituir meus métodos de contêiner por ações independentes. Eu ainda precisaria de um componente de contêiner para fornecer as ações para a camada de exibição de formulário, mas connect () e mapDispatchToProps me cobriram lá.

O novo v4 react-router-redux usa esse método para impactar o estado do histórico, por exemplo.

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

afinina
fonte
ótima resposta companheiro, você me impediu de fazer coisas sem cérebro 8) KUDOS !!
Csomakk
qual é o uso para exemplo de contêiner?
sensei
Não estou defendendo isso, mas se você quiser seguir o caminho do localizador de serviço (algo semelhante ao Angular), poderá adicionar algum tipo de provedor de "injetor / contêiner" do qual resolve os serviços (depois de os ter registrado anteriormente).
eddiewould
Ganchos de reação vem ao resgate. Com o Hooks, você pode escrever lógica reutilizável sem escrever uma classe. reactjs.org/docs/…
Raja Malik
102

O problema se torna extremamente simples quando você percebe que um serviço Angular é apenas um objeto que fornece um conjunto de métodos independentes de contexto. É apenas o mecanismo DI angular que faz com que pareça mais complicado. O DI é útil, pois cuida da criação e manutenção de instâncias para você, mas você realmente não precisa dele.

Considere uma biblioteca AJAX popular chamada axios (da qual você provavelmente já ouviu falar):

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

Não se comporta como um serviço? Ele fornece um conjunto de métodos responsáveis ​​por alguma lógica específica e é independente do código principal.

Seu exemplo de caso foi sobre a criação de um conjunto isolado de métodos para validar suas entradas (por exemplo, verificar a força da senha). Alguns sugeriram colocar esses métodos dentro dos componentes, o que para mim é claramente um anti-padrão. E se a validação envolver a realização e o processamento de chamadas de back-end XHR ou cálculos complexos? Você misturaria essa lógica com os manipuladores de cliques do mouse e outras coisas específicas da interface do usuário? Absurdo. O mesmo com a abordagem de contêiner / HOC. Embrulhando seu componente apenas para adicionar um método que verificará se o valor possui um dígito? Vamos.

Gostaria apenas de criar um novo arquivo chamado 'ValidationService.js' e organizá-lo da seguinte maneira:

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

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

export default ValidationService;

Então no seu 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 serviço de qualquer lugar que você desejar. Se as regras de validação mudarem, você precisará se concentrar apenas no arquivo ValidationService.js.

Você pode precisar de um serviço mais complicado, que depende de outros serviços. Nesse caso, seu arquivo de serviço pode retornar um construtor de classe em vez de um objeto estático, para que você possa criar uma instância do objeto no componente. Você também pode considerar implementar um singleton simples para garantir que sempre haja apenas uma instância do objeto de serviço em uso em todo o aplicativo.

Wojtek Majerski
fonte
3
É assim que eu também faria. Estou bastante surpreso que esta resposta tenha poucos votos, pois parece ser o caminho com menos atrito. Se o seu serviço depender de outros serviços, novamente, estaria importando esses outros serviços por meio de seus módulos. Além disso módulos são, por definição, singletons, então não há realmente nenhum trabalho adicional necessário para "implementá-lo como um singleton simples" - você tem que o comportamento de graça :)
Mickey Puri
6
+1 - Boa resposta se você estiver usando apenas serviços que fornecem funções. No entanto , o serviço da Angular são classes definidas uma vez, fornecendo mais recursos do que apenas entregar funções. Você pode armazenar em cache objetos como parâmetro de classe de serviço, por exemplo.
Nino Filiu
6
Esta deve ser a resposta real, e não a resposta complicada acima acima #
user1807334
1
Esta é uma boa resposta, exceto que não é "reativa". O DOM não será atualizado sobre alterações variáveis ​​dentro do serviço.
Defacto
9
E quanto à injeção de dependência? É impossível zombar do serviço em seu componente, a menos que você o injete de alguma forma. Talvez ter um objeto global de "contêiner" de nível superior que tenha cada serviço como um campo contornasse isso. Em seus testes, você pode substituir os campos do contêiner por zombarias dos serviços que deseja zombar.
menehune23
34

Eu precisava que alguma lógica de formatação fosse compartilhada entre vários componentes e, como desenvolvedor Angular, também naturalmente se inclinava para um serviço.

Compartilhei a lógica colocando-a em um arquivo separado

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

module.exports = {
    format: format
};

e depois importou-o como um módulo

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

//then in component

    render() {

        return formatter.format(this.props.data);
    }
Kildareflare
fonte
8
Essa é uma boa idéia, como mencionado no documento React: reactjs.org/docs/composition-vs-inheritance.html Se você deseja reutilizar a funcionalidade que não seja da interface do usuário entre os componentes, sugerimos extraí-la em um módulo JavaScript separado. Os componentes podem importá-lo e usar essa função, objeto ou classe, sem estendê-lo.
user3426603
Essa é realmente a única resposta aqui que faz sentido.
Artem Novikov
33

Lembre-se de que o objetivo do React é combinar melhor as coisas que logicamente devem ser acopladas. Se você estiver criando um método complicado de "validar senha", onde deve ser acoplado?

Bem, você precisará usá-lo sempre que o usuário precisar inserir uma nova senha. Pode estar na tela de registro, na tela "esqueci a senha", na tela "administrador redefinir a senha para outro usuário" etc.

Mas em qualquer um desses casos, ele sempre estará vinculado a algum campo de entrada de texto. Então é aí que deve ser acoplado.

Crie um componente React muito pequeno que consista apenas em um campo de entrada e na lógica de validação associada. Insira esse componente em todos os formulários que desejam ter uma entrada de senha.

É essencialmente o mesmo resultado de ter um serviço / fábrica para a lógica, mas você está acoplando-o diretamente à entrada. Portanto, agora você nunca precisa dizer a essa função onde procurar sua entrada de validação, pois ela está permanentemente ligada.

Jake Roby
fonte
11
O que é uma má prática associar lógica e interface do usuário. A fim de mudar a lógica vou ter que tocar o componente
Dennis Nerush
14
Reaja fundamentalmente aos desafios dessa suposição que você está fazendo. Está em forte contraste com a arquitetura MVC tradicional. Este vídeo explica muito bem por que motivo (a seção relevante começa em torno de 2 minutos).
Jake Roby
8
E se a mesma lógica de validação também precisar ser aplicada a um elemento da área de texto? A lógica ainda precisa ser extraída em um arquivo compartilhado. Não acho que exista alguma equivalência da biblioteca de reagentes. O serviço Angular é injetável e a estrutura Angular é construída sobre o padrão de design de injeção de dependência, que permite as instâncias das dependências gerenciadas pelo Angular. Quando um serviço é injetado, geralmente há um singleton no escopo fornecido, para ter o mesmo serviço no React, uma DI DI de terceiros precisa ser introduzida no aplicativo.
precisa
15
@gravityplanx Gosto de usar o React. Este não é um padrão angular, é um padrão de design de software. Eu gosto de manter minha mente aberta enquanto toma emprestado coisas que gosto de outras partes boas.
Downhillski
1
Os módulos do @MickeyPuri ES6 não são iguais à injeção de dependência.
Spock
12

Eu também vim da área Angular.js e os serviços e fábricas no React.js são mais simples.

Você pode usar funções simples ou classes, estilo de retorno de chamada e evento Mobx como eu :)

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

Aqui está um exemplo simples:

Juraj
fonte
React.js é uma biblioteca de interface do usuário para renderizar e organizar componentes da interface do usuário. Quando se trata de serviços que podem nos ajudar a adicionar funcionalidades adicionais, devemos criar coleções de funções, objetos funcionais ou classes. Achei as aulas muito úteis, mas sei que também estou jogando com um estilo funcional que também pode ser usado para criar ajudantes para adicionar funcionalidades vantajosas que estão fora do escopo do Reac.js.
Juraj
Apenas implementou isso. A maneira como você criou uma classe e a exportou é bastante elegante.
precisa saber é o seguinte
10

Mesma situação: ter realizado vários projetos Angular e mudar para o React, não ter uma maneira simples de fornecer serviços por meio do DI parece uma peça que falta (os detalhes do serviço à parte).

Usando decoradores de contexto e ES7, podemos chegar perto:

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

Parece que esses caras deram um passo adiante / em uma direção diferente:

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

Ainda parece trabalhar contra a corrente. Revisitará esta resposta em seis meses após a realização de um grande projeto React.

Edição: Voltar 6 meses depois, com um pouco mais de experiência React. Considere a natureza da lógica:

  1. Está vinculado (apenas) à interface do usuário? Mova-o para um componente (resposta aceita).
  2. Está ligado (apenas) à gestão do estado? Mova-o para um thunk .
  3. Amarrado a ambos? Mova para um arquivo separado, consuma no componente por meio de um seletor e em thunks.

Alguns também procuram HOCs para reutilização, mas, para mim, os itens acima abrangem quase todos os casos de uso. Além disso, considere dimensionar o gerenciamento de estado usando patos para manter as preocupações separadas e centralizar a interface do usuário no estado.

corola
fonte
Imho Eu acho que é uma maneira simples para fornecer serviços e atravesse DI, usando o sistema Módulo ES6
Mickey Puri
1
@MickeyPuri, o módulo ES6 DI não incluiria a natureza hierárquica do DI angular, ou seja. serviços de instanciação e substituição de pais (no DOM) fornecidos aos componentes filhos. O módulo DI do ES6 do Imho compara mais perto dos sistemas DI de back-end, como Ninject e Structuremap, mantendo-se à parte, em vez de basear-se na hierarquia de componentes do DOM. Mas eu gostaria de ouvir seus pensamentos sobre isso.
Corolla
6

Também sou da Angular e estou tentando experimentar o React, a partir de agora, uma maneira recomendada (?) Parece estar usando componentes de alta ordem :

Um componente de ordem superior (HOC) é uma técnica avançada no React para reutilizar a lógica do componente. Os HOCs não fazem parte da API do React, por si só. Eles são um padrão que emerge da natureza composicional de React.

Digamos que você tenha inputee textareagostaria de aplicar a mesma lógica de validação:

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

Em seguida, escreva um HOC que valide e denomine o componente agrupado:

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

Agora esses HOCs compartilham o mesmo comportamento de validação:

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

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

Eu criei uma demonstração simples .

Editar : Outra demonstração está usando adereços para passar uma matriz de funções, para que você possa compartilhar a lógica composta por várias funções de validação entre HOCs, como:

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

Edit2 : React 16.8+ fornece um novo recurso, Hook , outra boa maneira de compartilhar 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

prumo
fonte
Obrigado. Eu realmente aprendi com esta solução. E se eu precisar ter mais de um validador. Por exemplo, além do validador de três letras, e se eu quiser ter outro validador que garanta que nenhum número seja inserido. Podemos compor validadores?
Youssef Sherif
1
@YoussefSherif Você pode preparar várias funções de validação e passá-las como suporte HOC, veja minha edição para outra demonstração.
bob
então HOC é basicamente componente de contêiner?
sensei
Sim, no React doc: "Observe que um HOC não modifica o componente de entrada, nem usa herança para copiar seu comportamento. Em vez disso, um HOC compõe o componente original envolvendo-o em um componente de contêiner. Um HOC é puro função com zero efeitos colaterais ".
bob
1
O requisito era injetar lógica, não vejo por que precisamos de um HOC para fazer isso. Embora você possa fazer isso com um HOC, parece um pouco complicado. Meu entendimento dos HOCs é quando há também algum estado adicional que precisa ser adicionado e gerenciado, ou seja, não é pura lógica (como foi o caso aqui).
Mickey Puri
4

O serviço não se limita ao Angular, mesmo no Angular2 + ,

O serviço é apenas uma coleção de funções auxiliares ...

E há muitas maneiras de criá-los e reutilizá-los no aplicativo ...

1) Elas podem ser todas as funções separadas que são exportadas de um arquivo js, ​​semelhante ao abaixo:

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

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

2) Também podemos usar o método factory, como com a coleção de funções ... no ES6 , pode ser uma classe e não um construtor de funções:

class myService {

  constructor() {
    this._data = null;
  }

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

  getMyService() {
    return this._data;
  }

}

Nesse caso, você precisa criar uma instância com nova chave ...

const myServiceInstance = new myService();

Também neste caso, cada instância tem sua própria vida, portanto, tenha cuidado se quiser compartilhá-la, nesse caso, você deve exportar apenas a instância que deseja ...

3) Se sua função e utilitários não forem compartilhados, você pode até colocá-los no componente React, nesse caso, da mesma forma que no componente reage ...

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

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

4) Outra maneira de lidar com as coisas, pode ser usar o Redux , é uma loja temporária para você, portanto, se você o tiver no aplicativo React , ele poderá ajudá-lo com muitas funções de setter getter que você usa ... É como uma grande loja que mantêm o controle de seus estados e podem compartilhá-lo entre seus componentes, assim podem se livrar de muitos problemas causados ​​por animais que usamos nos serviços ...

É sempre bom criar um código DRY e não repetir o que precisa ser usado para tornar o código reutilizável e legível, mas não tente seguir as formas angulares no aplicativo React , como mencionado no item 4, o uso do Redux pode reduzir sua necessidade de serviços e você limita a usá-los para algumas funções auxiliares reutilizáveis, como o item 1 ...

Alireza
fonte
Claro, você pode encontrá-lo no meu site pessoal, que é o link da minha página de perfil ...
Alireza
"Não siga as formas angulares no React". A Angular promove o uso do Redux e transmite a loja para os componentes de apresentação usando o Observables e o gerenciamento de estado semelhante ao Redux, como o RxJS / Store. .. você quis dizer AngularJS? Porque isso é outra coisa
Spock
1

Estou na mesma bota como você. No caso mencionado, eu implementaria o componente de interface do usuário de validação de entrada como um componente React.

Concordo que a implementação da própria lógica de validação não deve (deve) ser acoplada. Portanto, eu o colocaria em um módulo JS separado.

Ou seja, para a lógica que não deve ser acoplada, use um módulo / classe JS em um arquivo separado e use require / import para desacoplar o componente do "serviço".

Isso permite injeção de dependência e teste de unidade dos dois independentemente.

sibidiba
fonte
1

ou você pode injetar a herança de classe "http" no React Component

via objeto adereços.

  1. update:

    ReactDOM.render(<ReactApp data={app} />, document.getElementById('root'));
  2. Basta editar o React Component ReactApp assim:

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

Bem, o padrão mais usado para a lógica reutilizável que encontrei é escrever um gancho ou criar um arquivo utils. Depende do que você deseja realizar.

hooks/useForm.js

Como se você quisesse validar os dados do formulário, eu criaria um gancho personalizado chamado useForm.js e forneceria os dados do formulário e, em troca, retornaria um objeto contendo duas coisas:

Object: {
    value,
    error,
}

Você pode definitivamente devolver mais coisas à medida que avança.

utils/URL.js

Outro exemplo seria como você deseja extrair algumas informações de um URL, então eu criaria um arquivo utils para ele contendo uma função e importaria quando necessário:

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