Posiadanie usług w aplikacji React

Pochodzę z kątowego świata, w którym mógłbym wydobyć logikę do serwisu/fabryki i zużyć ją w moich kontrolerach.

Próbuję zrozumieć, jak mogę osiągnąć to samo w aplikacji Reactowej.

Załóżmy, że mam komponent, który waliduje hasło Użytkownika (jego siłę). Logika jest dość skomplikowana, więc nie chcę tego pisać w komponencie.

Gdzie mam napisać tę logikę? W sklepie Jeśli używam flux? Czy jest lepszy opcja?

Author: Dennis Nerush, 2016-03-08

10 answers

Pierwsza odpowiedź nie odzwierciedla aktualnego paradygmatu Container vs Presenter .

Jeśli musisz coś zrobić, na przykład zweryfikować hasło, prawdopodobnie masz funkcję, która to robi. Przekazałbyś tę funkcję do widoku wielokrotnego użytku jako rekwizyt.

Pojemniki

Tak więc, poprawnym sposobem jest napisanie ValidatorContainer, który będzie miał tę funkcję jako właściwość, i zawinięcie w nią formularza, przekazując odpowiednie właściwości potomkowi. Jeśli chodzi o widok, kontener walidatora zawija widok, a Widok pochłania logikę kontenerów.

Walidacja może być wykonywana we właściwościach kontenera, ale jeśli używasz walidatora innej firmy lub dowolnej prostej usługi walidacji, możesz użyć usługi jako właściwości komponentu kontenera i użyć jej w metodach kontenera. Zrobiłem to dla komponentów restful i działa bardzo dobrze.

Dostawcy

Jeśli jest potrzeba trochę więcej konfiguracji, możesz może korzystać z modelu dostawcy / konsumenta. Dostawca jest komponentem wysokiego poziomu, który zawija się gdzieś blisko i pod górnym obiektem aplikacji (tym, który montujesz) i dostarcza część siebie lub właściwość skonfigurowaną w górnej warstwie do interfejsu API kontekstowego. Następnie ustawiam elementy kontenera tak, aby konsumowały kontekst.

Relacje kontekstowe rodzic / dziecko nie muszą być blisko siebie, po prostu dziecko musi być w jakiś sposób zstąpione. Redux przechowuje i funkcja Reactowego routera w tym sposób. Użyłem go do zapewnienia głównego kontekstu restful dla moich kontenerów rest (jeśli nie dostarczam własnych).

(Uwaga: API kontekstowe jest oznaczone jako experimental w dokumentach, ale nie wydaje mi się, aby już tak było, biorąc pod uwagę, co go używa).

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

Kolejnym sposobem, którego nie próbowałem, ale widziano go używanego, jest użycie middleware w połączeniu z Reduxem. Definiujesz obiekt usługi poza aplikacją, a przynajmniej wyżej niż sklep redux. Podczas tworzenia sklepu użytkownik wprowadza usługę do oprogramowania pośredniczącego, a oprogramowanie pośredniczące obsługuje wszelkie działania mające wpływ na usługę.

W ten sposób mógłbym wstrzyknąć sobie mój odpoczynek.obiekt js do middleware i zastąp moje metody kontenera niezależnymi akcjami. Nadal potrzebowałbym komponentu kontenera, aby dostarczyć akcje do warstwy widoku formularza, ale connect () i mapDispatchToProps mnie tam przykryły.

Nowy V4 react-router-redux używa tej metody, aby wpłynąć na stan historii, na przykład.

//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)
  }
}
 23
Author: aphenine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-14 18:30:31

Należy pamiętać, że celem Reacta jest lepsza para rzeczy, które logicznie powinny być ze sobą połączone. Jeśli projektujesz skomplikowaną metodę "zweryfikuj hasło", gdzie powinna być ona powiązana?

Cóż, będziesz musiał go używać za każdym razem, gdy użytkownik musi wprowadzić nowe hasło. Może to być na ekranie rejestracji, ekranie "Zapomniałem hasła", ekranie administratora "Resetuj hasło dla innego użytkownika" itp.

Ale w każdym z tych przypadków zawsze będzie remis do jakiegoś pola wprowadzania tekstu. Więc tam powinno być połączone.

Tworzy bardzo mały komponent Reactowy, który składa się wyłącznie z pola wejściowego i powiązanej logiki walidacji. Wprowadź Ten komponent we wszystkich formularzach, które mogą chcieć wprowadzić hasło.

Jest to zasadniczo taki sam wynik jak posiadanie usługi / fabryki dla logiki, ale łączysz ją bezpośrednio z wejściem. Więc teraz nie musisz mówić tej funkcji, gdzie szukać jej wejścia walidacji, ponieważ jest on trwale związany ze sobą.

 26
Author: gravityplanx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-08 04:24:48

Potrzebowałem logiki formatowania, aby była współdzielona między wieloma komponentami i jako programista kątowy naturalnie pochylił się w kierunku usługi.

Podzieliłem logikę umieszczając ją w osobnym pliku

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

module.exports = {
    format: format
};

A następnie zaimportował go jako moduł

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

//then in component

    render() {

        return formatter.format(this.props.data);
    }
 13
Author: Kildareflare,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-29 07:46:59

Ja też pochodzę z Angular.obszar js oraz usługi i fabryki w React.js są prostsze.

Możesz używać zwykłych funkcji lub klas, stylu callback i event Mobx jak ja:)

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

Oto prosty przykład:

 9
Author: Juraj Sarissky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-23 15:31:34

Ta sama sytuacja: po wykonaniu wielu kątowych projektów i przejściu do React, nie mając prostego sposobu świadczenia usług przez DI wydaje się brakującym elementem(szczegóły usługi na bok).

Używając dekoratorów context i ES7 możemy zbliżyć się:

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

Wygląda na to, że ci kolesie poszli o krok dalej / w innym Kierunek:

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

Nadal czuję, że pracuję przeciwko ziarnu. Powróci do tej odpowiedzi w ciągu 6 miesięcy po rozpoczęciu dużego projektu React.

EDIT: powrót 6 miesięcy później z większym doświadczeniem w reaccie. Rozważmy naturę logiki:

  1. czy jest powiązany (tylko) z interfejsem użytkownika? Przenieś go do komponentu (akceptowana odpowiedź).
  2. [[19]}czy jest to związane (tylko) z zarządzaniem Państwem? Move to wthunk . Przywiązany do obu? Przenieś do osobnego pliku, zużyj komponent przez selektor i thunks.

Niektórzy sięgają również poHOCs do ponownego użycia, ale dla mnie powyższe dotyczy prawie wszystkich przypadków użycia. Rozważ również skalowanie zarządzania stanem za pomocą ducks, aby utrzymać obawy oddzielnie i stan UI-centric.

 7
Author: corolla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-04 17:31:00

Problem staje się niezwykle prosty, gdy zdajesz sobie sprawę, że usługa kątowa jest tylko obiektem, który dostarcza zestaw metod niezależnych od kontekstu. To tylko mechanizm Angular DI, który sprawia, że wygląda bardziej skomplikowanie. DI jest przydatne, ponieważ zajmuje się tworzeniem i utrzymywaniem wystąpień, ale tak naprawdę go nie potrzebujesz.

Rozważ popularną bibliotekę AJAX o nazwie axios (o której prawdopodobnie słyszałeś):

import axios from "axios";
axios.post(...);
Czy to nie jest usługa? Zapewnia zestaw metody odpowiedzialne za jakąś specyficzną logikę i są niezależne od głównego kodu.

Twój przykład polegał na stworzeniu izolowanego zestawu metod sprawdzania poprawności danych wejściowych (np. sprawdzania siły hasła). Niektórzy sugerowali, aby umieścić te metody wewnątrz komponentów, które dla mnie jest wyraźnie anty-wzór. Co zrobić, jeśli Walidacja wymaga wykonywania i przetwarzania wywołań backendowych XHR lub wykonywania złożonych obliczeń? Czy wymieszać tę logikę z obsługi kliknięć myszy i innych interfejsów użytkownika konkretne rzeczy? Nonsens. To samo dotyczy podejścia container / HOC. Owijanie komponentu tylko po to, aby dodać metodę, która sprawdzi, czy wartość ma w sobie cyfrę? Chodź.

Chciałbym po prostu utworzyć nowy plik o nazwie say ' ValidationService.js " i zorganizować go w następujący sposób:

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

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

export default ValidationService;

Następnie w składniku:

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

...

//inside the component
yourInputChangeHandler(event) {

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

Korzystaj z tej usługi z dowolnego miejsca. Jeśli zasady walidacji ulegną zmianie, musisz skupić się na ValidationService.tylko plik js.

Możesz potrzebować bardziej skomplikowana usługa, która zależy od innych usług. W takim przypadku plik service może zwrócić konstruktor klasy zamiast obiektu statycznego, dzięki czemu można samodzielnie utworzyć instancję obiektu w komponencie. Można również rozważyć implementację prostego Singletona, aby upewnić się, że zawsze jest używana tylko jedna instancja obiektu service w całej aplikacji.
 6
Author: Wojciech Majerski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-21 23:30:51

Serwis nie ogranicza się do kątowych, nawet w Kątowych2+,

Usługa jest tylko zbiorem funkcji pomocniczych...

I istnieje wiele sposobów na ich tworzenie i ponowne wykorzystanie w całej aplikacji...

1) mogą to być wszystkie oddzielone funkcje, które są eksportowane z pliku js, podobnie jak poniżej:

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

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

2) możemy również użyć metody fabrycznej, takiej jak, ze zbiorem funkcji... z ES6 może to być klasa, a nie konstruktor funkcji:

class myService {

  constructor() {
    this._data = null;
  }

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

  getMyService() {
    return this._data;
  }

}

W tym przypadku musisz utworzyć instancję z nowym kluczem...

const myServiceInstance = new myService();

Również w tym przypadku, każda instancja ma swoje własne życie, więc bądź ostrożny, jeśli chcesz je udostępnić, w takim przypadku powinieneś eksportować tylko wybraną instancję...

3) Jeśli twoja funkcja i utils nie będą współdzielone, możesz nawet umieścić je w reactowym komponencie, w tym przypadku, tak samo jak funkcja w Twoim reactowym komponencie...

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

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

4) w inny sposób możesz obsługuje rzeczy, może używać Redux , jest to tymczasowy sklep dla ciebie, więc jeśli masz go w swojej aplikacji Reactowej , może Ci pomóc z wieloma funkcjami getter setter , których używasz... To jak duży sklep, który przechowuje ślady twoich stanów i może udostępniać je między twoimi komponentami, dzięki czemu może pozbyć się wielu bólu dla Getter setter stuffs, których używamy w usługach...

Zawsze dobrze jest zrobić suchy kod i nie powtarzać tego, co należy użyć do stworzenia kodu react app , Jak wspomniano w punkcie 4, Korzystanie z Redux może zmniejszyć zapotrzebowanie na usługi i ograniczyć korzystanie z nich do niektórych funkcji pomocniczych, takich jak punkt 1...

 2
Author: Alireza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-13 01:45:49

Ja też jestem z Angular i wypróbowuję Reacta, jak na razie polecam (?) way wydaje się używać komponentów wysokiego rzędu :

Komponent wyższego rzędu (HOC) jest zaawansowaną techniką w Reaccie dla ponowne wykorzystanie logiki komponentów. HOCs nie są częścią Reactowego API, samo w sobie. Są wzorcem, który wyłania się z kompozycji Reacta.

Załóżmy, że masz input i textarea i lubisz stosować tę samą logikę walidacji:

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

Wtedy napisz HOC, który nie waliduje i styl zawinięty komponent:

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

Teraz te HOCs mają to samo zachowanie walidacyjne:

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

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

Stworzyłem proste demo .

Edit : kolejne demo używa właściwości do przekazywania tablicy funkcji, dzięki czemu można współdzielić logikę złożoną z wielu funkcji walidujących w HOC takich jak:

<InputWithValidator validators={[validator1,validator2]} />
<TextAreaWithValidator validators={[validator1,validator2]} />
 2
Author: bob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-26 19:10:35

Jestem w tym samym bucie jak ty. W przypadku, gdy wspomnisz, zaimplementowałbym komponent input validation UI jako komponent Reactowy.

Zgadzam się, że implementacja samej logiki walidacji nie powinna (musi) być powiązana. Dlatego umieściłbym go w osobnym module JS.

To znaczy dla logiki, która nie powinna być sprzężona użyj modułu/klasy JS w oddzielnym pliku i użyj require / import, aby odłączyć komponent od "usługi".

Pozwala to na zależność testowanie wtrysku i jednostkowe tych dwóch niezależnie.

 1
Author: sibidiba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-13 05:03:04

Lub możesz wprowadzić dziedziczenie klasy "http" do komponentu Reactowego

Poprzez obiekt props.

  1. Aktualizacja:

    ReactDOM.render(<ReactApp data={app} />, document.getElementById('root'));
    
  2. Po prostu Edytuj komponent Reactapp w następujący sposób:

    class ReactApp extends React.Component {
    
    state = {
    
        data: ''
    
    }
    
        render(){
    
        return (
            <div>
            {this.props.data.getData()}      
            </div>
    
        )
        }
    }
    
 1
Author: Juraj Sarissky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-17 02:06:11