Czy sklepy lub akcje flux powinny dotyczyć usług zewnętrznych?


122

Czy sklepy utrzymują swój własny stan i mają możliwość wywoływania usług sieciowych i przechowywania danych w ten sposób ... w takim przypadku działania są po prostu głupimi odbiorcami wiadomości,

-LUB-

... czy sklepy powinny być głupimi odbiorcami niezmiennych danych z działań (a akcjami powinny być te, które pobierają / wysyłają dane między źródłami zewnętrznymi? Sklep w tym przypadku działałby jako modele widoków i byłby w stanie agregować / filtrować ich dane przed ustanowieniem własnej bazy stanu na niezmiennych danych, które były zasilane przez działanie.

Wydaje mi się, że powinien to być jeden lub drugi (a nie mieszanka obu). Jeśli tak, dlaczego jeden jest preferowany / zalecany w stosunku do drugiego?


2
Ten post może pomóc code-experience.com/…
Markus-ipse

Osobom oceniającym różne implementacje wzorca strumienia gorąco polecam zajrzenie na stronę Redux github.com/rackt/redux. Sklepy są implementowane jako czyste funkcje, które przyjmują bieżący stan i emitują nową wersję tego stanu. Ponieważ są one czystymi funkcjami, kwestia, czy mogą dzwonić do usług sieciowych i magazynowych, jest wyrwana z twoich rąk: nie mogą.
plaxdan

Odpowiedzi:


151

Widziałem wzorzec strumienia zaimplementowany w obie strony i po zrobieniu obu (początkowo stosując poprzednie podejście) uważam, że sklepy powinny być głupimi odbiorcami danych z akcji, a asynchroniczne przetwarzanie zapisów powinno żyć w twórcy akcji. ( Odczyty asynchroniczne mogą być obsługiwane inaczej ). Z mojego doświadczenia wynika, że ​​ma to kilka zalet, w kolejności od najważniejszych:

  1. Twoje sklepy stają się całkowicie synchroniczne. Dzięki temu logika sklepu jest znacznie łatwiejsza do śledzenia i bardzo łatwa do testowania - wystarczy utworzyć instancję sklepu z określonym stanem, wysłać mu akcję i sprawdzić, czy stan zmienił się zgodnie z oczekiwaniami. Ponadto jedną z podstawowych koncepcji przepływu jest zapobieganie kaskadowym wysyłkom i zapobieganie wielu wysyłkom jednocześnie; Jest to bardzo trudne, gdy Twoje sklepy przetwarzają dane asynchronicznie.

  2. Wszystkie wywołania akcji pochodzą od twórców akcji. Jeśli obsługujesz operacje asynchroniczne w swoich sklepach i chcesz, aby moduły obsługi akcji sklepów były synchroniczne (i powinieneś, aby uzyskać gwarancje przepływu w pojedynczej wysyłce), Twoje sklepy będą musiały uruchomić dodatkowe akcje SUCCESS i FAIL w odpowiedzi na asynchroniczne przetwarzanie. Zamiast tego umieszczenie tych rozkazów w kreatorach akcji pomaga oddzielić zadania twórców akcji od sklepów; co więcej, nie musisz przekopywać się przez logikę sklepu, aby dowiedzieć się, skąd wysyłane są akcje. Typowa asynchroniczna akcja w tym przypadku może wyglądać mniej więcej tak (zmień składnię dispatchwywołań na podstawie stylu używanego strumienia):

    someActionCreator: function(userId) {
      // Dispatch an action now so that stores that want
      // to optimistically update their state can do so.
      dispatch("SOME_ACTION", {userId: userId});
    
      // This example uses promises, but you can use Node-style
      // callbacks or whatever you want for error handling.
      SomeDataAccessLayer.doSomething(userId)
      .then(function(newData) {
        // Stores that optimistically updated may not do anything
        // with a "SUCCESS" action, but you might e.g. stop showing
        // a loading indicator, etc.
        dispatch("SOME_ACTION_SUCCESS", {userId: userId, newData: newData});
      }, function(error) {
        // Stores can roll back by watching for the error case.
        dispatch("SOME_ACTION_FAIL", {userId: userId, error: error});
      });
    }

    Logika, która w przeciwnym razie mogłaby zostać zduplikowana w różnych działaniach, powinna zostać wyodrębniona do osobnego modułu; w tym przykładzie byłby to moduł SomeDataAccessLayer, który obsługuje rzeczywiste żądanie Ajax.

  3. Potrzebujesz mniej twórców akcji. To mniej wielka sprawa, ale miło mieć. Jak wspomniano w punkcie 2, jeśli Twoje sklepy mają obsługę wysyłania akcji synchronicznych (a powinny), będziesz musiał uruchomić dodatkowe akcje, aby obsłużyć wyniki operacji asynchronicznych. Wykonywanie poleceń w kreatorze akcji oznacza, że ​​twórca pojedynczej akcji może wywołać wszystkie trzy typy akcji, obsługując sam wynik asynchronicznego dostępu do danych.


15
Myślę, że to, co zapoczątkowuje wywołanie interfejsu API sieci Web (kreator akcji a sklep), jest mniej ważne niż fakt, że wywołanie zwrotne sukcesu / błędu powinno utworzyć akcję. Zatem przepływ danych to zawsze: akcja -> dyspozytor -> sklepy -> widoki.
fisherwebdev

1
Czy umieszczenie rzeczywistej logiki żądania w module API byłoby lepsze / łatwiejsze do przetestowania? Więc twój moduł API może po prostu zwrócić obietnicę, z której wysyłasz. Twórca akcji wysyła po prostu na podstawie rozwiązania / niepowodzenia po wysłaniu początkowej „oczekującej” akcji. Pozostaje pytanie, w jaki sposób komponent nasłuchuje tych „zdarzeń”, ponieważ nie jestem pewien, czy stan żądania powinien być mapowany na stan przechowywania.
backdesk

@backdesk Dokładnie to robię w powyższym przykładzie: wysyłam początkową oczekującą akcję ( "SOME_ACTION"), używam API, aby wykonać request ( SomeDataAccessLayer.doSomething(userId)), które zwraca obietnicę, aw obu .thenfunkcjach wysyłam dodatkowe akcje. Stan żądania może (mniej więcej) mapować do stanu przechowywania, jeśli aplikacja musi wiedzieć o stanie stanu. Jak ta mapa jest zależna od aplikacji (np. Może każdy komentarz ma indywidualny stan błędu, a la Facebook, a może jest jeden komponent globalnego błędu)
Michelle Tilley

@MichelleTilley „Jedną z podstawowych koncepcji flux jest zapobieganie wysyłkom kaskadowym i zapobieganie wielu wysyłkom naraz; jest to bardzo trudne, gdy Twoje sklepy przetwarzają asynchronicznie”. To dla mnie kluczowa kwestia. Dobrze powiedziane.

51

Napisałem na Twitterze to pytanie do deweloperów na Facebooku, a odpowiedź, którą otrzymałem od Billa Fishera, brzmiała:

Odpowiadając na interakcję użytkownika z interfejsem użytkownika, wykonałbym wywołanie asynchroniczne w metodach kreatora akcji.

Ale jeśli masz ticker lub inny kierowca niebędący człowiekiem, połączenie ze sklepu działa lepiej.

Ważne jest, aby utworzyć akcję w wywołaniu zwrotnym błędu / sukcesu, aby dane zawsze pochodziły z akcji


Chociaż ma to sens, każdy pomysł, dlaczego a call from store works better when action triggers from non-human driver ?
SharpCoder,

@SharpCoder Myślę, że jeśli masz live-ticker lub coś podobnego, tak naprawdę nie musisz uruchamiać akcji, a kiedy robisz to ze sklepu, prawdopodobnie musisz napisać mniej kodu, ponieważ sklep może natychmiast uzyskać dostęp do stanu i dokonaj zmiany.
Florian Wendelborn

8

Sklepy powinny zrobić wszystko, łącznie z pobieraniem danych i sygnalizowaniem komponentom, że dane sklepu zostały zaktualizowane. Czemu? Ponieważ działania mogą być wtedy lekkie, jednorazowe i wymienne bez wpływu na ważne zachowanie. Wszystkie ważne zachowania i funkcjonalności mają miejsce w sklepie. Zapobiega to również powielaniu zachowań, które w innym przypadku zostałyby skopiowane w dwóch bardzo podobnych, ale różnych akcjach. Sklepy są Twoim singlem źródłem prawdy.

W każdej implementacji Flux, którą widziałem, akcje są w zasadzie łańcuchami zdarzeń zamienianymi w obiekty, tak jak tradycyjnie miałbyś zdarzenie o nazwie „anchor: clicked”, ale we Flux byłoby zdefiniowane jako AnchorActions.Clicked. Są nawet tak „głupi”, że większość implementacji ma oddzielne obiekty Dispatcher, które faktycznie wysyłają zdarzenia do nasłuchujących sklepów.

Osobiście podoba mi się implementacja Flux firmy Reflux, w której nie ma oddzielnych obiektów Dispatcher, a obiekty Action same wykonują wysyłkę.


edit: Facebook Flux faktycznie pobiera w "twórcach akcji", więc używają inteligentnych akcji. Przygotowują również ładunek za pomocą sklepów:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/actions/ChatMessageActionCreators.js#L27 (linia 27 i 28)

Wywołanie zwrotne po zakończeniu wywołałoby tym razem nowe działanie z pobranymi danymi jako ładunkiem:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/utils/ChatWebAPIUtils.js#L51

Więc myślę, że to lepsze rozwiązanie.


Co to za realizacja Reflux? Nie słyszałem o tym. Twoja odpowiedź jest interesująca. Masz na myśli, że implementacja Twojego sklepu powinna mieć logikę do wykonywania wywołań API i tak dalej? Pomyślałem, że sklepy powinny po prostu otrzymać dane i zaktualizować swoje wartości. Filtrują według określonych działań i aktualizują niektóre atrybuty swoich sklepów.
Jeremy D

Reflux to niewielka odmiana Fluxa Facebooka: github.com/spoike/refluxjs Sklepy zarządzają całą domeną „Model” Twojej aplikacji, w przeciwieństwie do Akcji / Dyspozytorów, którzy tylko łączą i sklejają elementy.
Rygu

1
Więc myślałem o tym więcej i (prawie) odpowiedziałem na moje własne pytanie. Dodałbym to jako odpowiedź tutaj (aby inni mogli głosować), ale najwyraźniej jestem zbyt ubogi w karmę w przepływie stosów, aby móc jeszcze opublikować odpowiedź. Oto link: groups.google.com/d/msg/reactjs/PpsvVPvhBbc/BZoG-bFeOwoJ
plaxdan

Dzięki za link do grupy google, wydaje się naprawdę pouczający. Jestem też bardziej fanem wszystkiego, co przechodzi przez dyspozytora, i naprawdę prostej logiki w sklepie, w zasadzie aktualizowanie ich danych to wszystko. @Rygu sprawdzę refluks.
Jeremy D

Zmieniłem odpowiedź w innym widoku. Wydaje się, że oba rozwiązania są możliwe. Prawie na pewno wybrałbym rozwiązanie Facebooka zamiast innych.
Rygu

3

Przedstawię argument za „głupimi” działaniami.

Umieszczając odpowiedzialność za gromadzenie danych widoków w Akcjach, łączysz swoje Akcje z wymaganiami dotyczącymi danych w Twoich widokach.

W przeciwieństwie do tego, Akcje ogólne, które deklaratywnie opisują intencję użytkownika lub zmianę stanu w aplikacji, umożliwiają dowolnemu Sklepowi, który reaguje na tę akcję, przekształcenie intencji w stan dostosowany specjalnie do subskrybowanych widoków.

To nadaje się do większej liczby, ale mniejszych, bardziej wyspecjalizowanych Sklepów. Opowiadam się za tym stylem, ponieważ

  • Zapewnia to większą elastyczność w sposobie wykorzystania danych Sklepu przez widoki
  • „inteligentne” sklepy, specjalizujące się w widokach, które je konsumują, będą mniejsze i mniej powiązane ze złożonymi aplikacjami niż „inteligentne” działania, od których zależy potencjalnie wiele widoków

Celem Sklepu jest udostępnianie danych do wyświetleń. Nazwa „Działanie” sugeruje mi, że jej celem jest opisanie zmiany w mojej Aplikacji.

Załóżmy, że musisz dodać widżet do istniejącego widoku pulpitu nawigacyjnego, który pokazuje nowe, fantazyjne dane zagregowane, które właśnie wprowadził zespół zaplecza.

W przypadku „inteligentnych” działań może być konieczna zmiana akcji „odśwież-panel informacyjny”, aby korzystać z nowego interfejsu API. Jednak „Odświeżanie dashboardu” w sensie abstrakcyjnym nie uległo zmianie. Zmieniły się wymagania dotyczące danych w Twoich widokach.

Za pomocą „głupich” akcji możesz dodać nowy Sklep do wykorzystania przez nowy widżet i skonfigurować go tak, aby po otrzymaniu typu działania „odśwież-dashboard” wysyłał żądanie nowych danych i pokazywał je nowy widget, gdy będzie gotowy. Wydaje mi się sensowne, że gdy warstwa widoku potrzebuje więcej lub innych danych, rzeczy, które zmieniam, są źródłami tych danych: Sklepy.


2

Flux-React -Router-demo firmy Gaeron ma przyjemną odmianę użytkową „prawidłowego” podejścia.

ActionCreator generuje obietnicę z zewnętrznej usługi API, a następnie przekazuje obietnicę i trzy stałe akcji do dispatchAsyncfunkcji w proxy / rozszerzonym Dispatcherze. dispatchAsynczawsze wyśle ​​pierwszą akcję, np. „GET_EXTERNAL_DATA”, a po zwróceniu obietnicy wyśle ​​„GET_EXTERNAL_DATA_SUCCESS” lub „GET_EXTERNAL_DATA_ERROR”.


1

Jeśli chcesz pewnego dnia mieć środowisko programistyczne porównywalne z tym, co widzisz w słynnym filmie Bret Victor Inventing on Principle , powinieneś raczej używać głupich sklepów, które są po prostu projekcją działań / zdarzeń wewnątrz struktury danych, bez żadnych skutków ubocznych. Pomogłoby również, gdyby twoje sklepy były faktycznie członkami tej samej globalnej niezmiennej struktury danych, jak w Redux .

Więcej wyjaśnień tutaj: https://stackoverflow.com/a/31388262/82609

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.