Gdzie należy złożyć wniosek ajax w aplikacji Flux?

194

Tworzę aplikację reag.js z architekturą Flux i próbuję dowiedzieć się, gdzie i kiedy należy złożyć żądanie danych z serwera. Czy jest na to jakiś przykład. (Nie aplikacja TODO!)

Eniz Gülek
źródło

Odpowiedzi:

127

Jestem wielkim zwolennikiem umieszczania operacji zapisu asynchronicznego w twórcach akcji i operacji odczytu asynchronicznego w sklepie. Celem jest utrzymanie kodu modyfikacji stanu sklepu w pełni synchronicznych modułach obsługi akcji; dzięki temu są łatwe do uzasadnienia i łatwe do przeprowadzenia testu jednostkowego. Aby zapobiec wielu jednoczesnym żądaniom do tego samego punktu końcowego (na przykład podwójne czytanie), przeniesię przetwarzanie rzeczywistych żądań do osobnego modułu, który używa obietnic, aby zapobiec wielu żądaniom; na przykład:

class MyResourceDAO {
  get(id) {
    if (!this.promises[id]) {
      this.promises[id] = new Promise((resolve, reject) => {
        // ajax handling here...
      });
    } 
    return this.promises[id];
  }
}

Choć brzmi w sklepie obejmować funkcje asynchroniczne, istnieje istotne zastrzeżenie, że sklepy nie aktualizują się w teleskopowe asynchronicznych, ale zamiast strzelać działanie i tylko odpalić akcję kiedy nadejdzie odpowiedź. Procedury obsługi tej akcji kończą rzeczywistą modyfikację stanu.

Na przykład składnik może:

getInitialState() {
  return { data: myStore.getSomeData(this.props.id) };
}

W sklepie może być zaimplementowana metoda, może taka:

class Store {
  getSomeData(id) {
    if (!this.cache[id]) {
      MyResurceDAO.get(id).then(this.updateFromServer);
      this.cache[id] = LOADING_TOKEN;
      // LOADING_TOKEN is a unique value of some kind
      // that the component can use to know that the
      // value is not yet available.
    }

    return this.cache[id];
  }

  updateFromServer(response) {
    fluxDispatcher.dispatch({
      type: "DATA_FROM_SERVER",
      payload: {id: response.id, data: response}
    });
  }

  // this handles the "DATA_FROM_SERVER" action
  handleDataFromServer(action) {
    this.cache[action.payload.id] = action.payload.data;
    this.emit("change"); // or whatever you do to re-render your app
  }
}
Michelle Tilley
źródło
Czy próbowałeś złożyć obietnice w ładunkach akcji? Łatwiej mi sobie radzić niż wysyłać wiele akcji
Sebastien Lorber
@SebastienLorber Największym problemem dla mnie jest utrzymanie wszystkich aktualizacji stanu w synchronicznej ścieżce kodu i jawnie tylko w wyniku wysłania akcji, więc unikam asynchronii wewnątrz sklepów.
Michelle Tilley
1
@Federico Nadal nie jest dla mnie jasne, jakie jest „najlepsze” rozwiązanie. Eksperymentowałem z tą strategią ładowania danych w połączeniu z liczeniem zaległych żądań asynchronicznych. Niestety po fluxwstrzyknięciu trafia do sklepów, więc nie ma świetnego sposobu na rozpoczęcie akcji metodą inicjalizacji. Możesz znaleźć dobre pomysły z izomoroficznych bibliotek strumienia Yahoo; jest to coś, co Fluxxor v2 powinien lepiej obsługiwać. Nie krępuj się napisz do mnie, jeśli chcesz porozmawiać o tym więcej.
Michelle Tilley,
1
data: resultpowinno być data : data, prawda? Nie ma result. być może lepiej zmienić nazwę parametru danych na ładunek lub coś w tym rodzaju.
oligofren,
2
Uważam, że ten stary wątek był bardzo pomocny - w szczególności komentarze Billa Fishera i Jing Chena. Jest to bardzo zbliżone do tego, co proponuje @BinaryMuse, z niewielką różnicą, że wysyłanie odbywa się w kreatorze akcji.
phillipwei
37

Fluxxor ma przykład asynchronicznej komunikacji z API.

Ten post na blogu mówi o tym i został opisany na blogu React.


Uważam to za bardzo ważne i trudne pytanie, na które jeszcze nie ma jednoznacznej odpowiedzi, ponieważ synchronizacja oprogramowania frontendu z backendem jest nadal uciążliwa.

Czy żądania API powinny być składane w komponentach JSX? Sklepy? Inne miejsce?

Wykonywanie żądań w sklepach oznacza, że ​​jeśli 2 sklepy potrzebują tych samych danych dla danej akcji, wydadzą 2 podobne wymagania (chyba że wprowadzisz zależności między sklepami, których tak naprawdę nie lubię )

W moim przypadku jest to bardzo przydatne, aby umieścić obietnice Q jako ładunek działań, ponieważ:

  • Moje działania nie muszą być serializowane (nie prowadzę dziennika zdarzeń, nie potrzebuję funkcji odtwarzania zdarzeń w przypadku pozyskiwania zdarzeń)
  • Eliminuje to potrzebę posiadania różnych akcji / zdarzeń (żądanie uruchomione / żądanie zakończone / żądanie nie powiodło się) i trzeba je dopasować za pomocą identyfikatorów korelacji, gdy można uruchomić równoczesne żądania.
  • Pozwala wielu sklepom wysłuchać wykonania tego samego żądania, bez wprowadzania zależności między sklepami (jednak może lepiej wprowadzić warstwę buforującą?)

Ajax jest ZŁY

Myślę, że Ajax będzie coraz mniej używany w najbliższej przyszłości, ponieważ bardzo trudno jest o tym myśleć. Właściwy sposób? Biorąc pod uwagę urządzenia jako część systemu rozproszonego, nie wiem, gdzie po raz pierwszy natknąłem się na ten pomysł (może w tym inspirującym filmie Chrisa Grangera ).

Pomyśl o tym. Teraz dla skalowalności używamy systemów rozproszonych z ostateczną spójnością jako mechanizmów pamięci masowej (ponieważ nie możemy pokonać twierdzenia CAP i często chcemy być dostępni). Systemy te nie synchronizują się poprzez wzajemne odpytywanie (może poza operacjami konsensusu?), Ale raczej używają struktur takich jak CRDT i dzienniki zdarzeń, aby ostatecznie wszyscy członkowie systemu rozproszonego byli spójni (członkowie zbiegną się do tych samych danych, mając wystarczająco dużo czasu) .

Teraz zastanów się, co to jest urządzenie mobilne lub przeglądarka. Jest tylko członkiem systemu rozproszonego, który może cierpieć z powodu opóźnień sieci i partycjonowania sieci. (tzn. używasz smartfona w metrze)

Jeśli uda nam się zbudować bazy danych z partycjami sieciowymi i bazami tolerującymi prędkość sieci (mam na myśli, że nadal możemy wykonywać operacje zapisu w izolowanym węźle), prawdopodobnie możemy zbudować oprogramowanie frontendowe (mobilne lub stacjonarne) zainspirowane tymi koncepcjami, które działają dobrze z obsługiwanym trybem offline opakowania bez funkcji niedostępności aplikacji.

Myślę, że powinniśmy naprawdę zainspirować się tym, w jaki sposób bazy danych pracują nad architekturą naszych aplikacji frontendowych. Należy zauważyć, że te aplikacje nie wykonują żądań POST oraz PUT i GET ajax w celu wysyłania danych do siebie, ale raczej używają dzienników zdarzeń i CRDT, aby zapewnić ostateczną spójność.

Dlaczego więc nie zrobić tego na interfejsie? Zauważ, że backend już przesuwa się w tym kierunku, a narzędzia takie jak Kafka są masowo adoptowane przez dużych graczy. Jest to również w jakiś sposób powiązane z Event Sourcing / CQRS / DDD.

Sprawdź te niesamowite artykuły od autorów Kafki, aby się przekonać:

Być może możemy zacząć od wysyłania poleceń do serwera i odbierania strumienia zdarzeń serwera (na przykład przez websockets) zamiast uruchamiania żądań Ajax.

Nigdy nie czułem się dobrze z prośbami Ajax. Gdy reagujemy, programiści są zwykle programistami funkcjonalnymi. Myślę, że trudno jest uzasadnić dane lokalne, które powinny być „źródłem prawdy” aplikacji frontendowej, podczas gdy prawdziwe źródło prawdy znajduje się w bazie danych serwera, a „lokalne” źródło prawdy może być już nieaktualne kiedy go otrzymasz, i nigdy nie zbliży się do prawdziwego źródła wartości prawdy, chyba że naciśniesz jakiś kiepski przycisk Odśwież ... Czy to inżynieria?

Nadal jednak trudno jest zaprojektować coś takiego z oczywistych powodów:

  • Twój klient mobilny / przeglądarkowy ma ograniczone zasoby i niekoniecznie może przechowywać wszystkie dane lokalnie (dlatego czasami wymaga odpytywania przy użyciu dużej zawartości żądania ajax)
  • Twój klient nie powinien widzieć wszystkich danych w systemie rozproszonym, dlatego wymaga filtrowania zdarzeń, które otrzymuje ze względów bezpieczeństwa
Sebastien Lorber
źródło
3
Czy możesz podać przykład użycia obietnic Q z działaniami?
Matt Foxx Duncan
@MattFoxxDuncan nie jest pewien, czy to dobry pomysł, ponieważ sprawia, że ​​„dziennik zdarzeń” jest nieserializowalny i sprawia, że ​​sklep aktualizuje się asynchronicznie po uruchomieniu akcji, więc ma pewne wady. Jednak jeśli jest to odpowiednie dla twojej skrzynki użytkownika i rozumiesz te wady, jest to dość przydatne i zmniejszyć płytę grzewczą. Z Fluxxorem prawdopodobnie możesz zrobić coś takiegothis.dispatch("LOAD_DATA", {dataPromise: yourPromiseHere});
Sebastien Lorber,
Całkowicie nie zgadzam się z argumentem AJAX. W rzeczywistości czytanie było bardzo denerwujące. Czy czytałeś swoje uwagi? Pomyśl o sklepach, grach, aplikacjach, które zarabiają poważne pieniądze - wszystkie wymagają API i połączeń z serwerem AJAX. Spójrz na Firebase, jeśli chcesz „bez serwera” lub coś takiego, ale AJAX jest tutaj, by powiedzieć, że mam nadzieję, że przynajmniej nikt się z tym nie zgadza twoja logika
TheBlackBenzKid
@TheBlackBenzKid Nie mówię, że Ajax zniknie całkowicie w ciągu roku (i upewnij się, że nadal buduję strony internetowe na podstawie próśb o ajax obecnie jako CTO startupu), ale mówię, że prawdopodobnie zniknie, ponieważ nie jest to protokół wystarczająco dobry, aby poradzić sobie z ostateczną spójnością, która raczej wymaga przesyłania strumieniowego, a nie odpytywania, a ostateczna spójność pozwala na niezawodne działanie aplikacji offline (tak, możesz samodzielnie zhakować coś za pomocą magazynu lokalnego, ale będziesz mieć ograniczone możliwości offline lub Twoja aplikacja jest bardzo prosta). Problemem nie jest buforowanie, unieważnia to buforowanie.
Sebastien Lorber
@TheBlackBenzKid Modele Firebase, Meteor itp. Nie są wystarczająco dobre. Czy wiesz, jak te systemy obsługują równoczesne zapisy? ostatnia wygrana-wygrana zamiast przyczynowej spójności / strategii łączenia? Czy możesz sobie pozwolić na nadrzędną pracę swojego kolegi w aplikacji, gdy obie pracują na nierzetelnych połączeniach? Należy również pamiętać, że systemy te często łączą modelowanie lokalne i modelowanie serwerów. Czy znasz dobrze znaną aplikację do współpracy, która jest znacznie złożona, działa idealnie w trybie offline, deklarując, że jest zadowolonym użytkownikiem Firebase? Nie wiem
Sebastien Lorber
20

Możesz wezwać dane w kreatorze akcji lub w sklepach. Ważne jest, aby nie obsługiwać odpowiedzi bezpośrednio, ale utworzyć akcję w wywołaniu zwrotnym błędu / sukcesu. Obsługa odpowiedzi bezpośrednio w sklepie prowadzi do bardziej kruchego projektu.

fisherwebdev
źródło
9
Czy możesz to wyjaśnić bardziej szczegółowo, proszę? Powiedz, że muszę wykonać wstępne ładowanie danych z serwera. W widoku kontrolera uruchamiam akcję INIT, a Sklep rozpoczyna inicjalizację asynchroniczną odzwierciedlającą tę akcję. Teraz przyszedłby mi do głowy pomysł, że gdy Sklep pobiera dane, po prostu emituje zmianę, ale nie rozpoczyna działania. Dlatego wysłanie zmiany po inicjalizacji informuje widoki, że mogą pobrać dane ze sklepu. Dlaczego istnieje potrzeba nie emitują zmiany po pomyślnym załadunku, ale wychodząc kolejną akcję ?! Dzięki
Jim-Y,
Fisherwebdev, o sklepach wzywających dane, robiąc to, nie łamiesz paradygmatu Fluxa, jedynymi 2 właściwymi sposobami, które mogę wymyślić, aby wywołać dane, są: 1. Użyj klasy bootstrap za pomocą Akcji, aby załadować dane 2 , Widoki, ponownie za pomocą Akcje do ładowania danych
Yotam
4
Wzywanie danych to nie to samo, co odbieranie danych. @ Jim-Y: powinieneś wyemitować zmianę dopiero po zmianie danych w sklepie. Yotam: Nie, wołanie o dane w sklepie nie narusza paradygmatu. Dane powinny być odbierane tylko poprzez działania, aby wszystkie sklepy mogły być informowane o wszelkich nowych danych wchodzących do aplikacji. Możemy więc wezwać dane w sklepie, ale kiedy odpowiedź wróci, musimy utworzyć nową akcję zamiast bezpośrednio ją obsługiwać. Dzięki temu aplikacja jest elastyczna i odporna na rozwój nowych funkcji.
fisherwebdev
2

Korzystam z przykładu Binary Muse z przykładu Fluxxor ajax . Oto mój bardzo prosty przykład z tym samym podejściem.

Mam prosty sklep z niektórymi akcjami produktu i komponent widoku kontrolera , który zawiera podskładniki, które wszystkie reagują na zmiany wprowadzone w sklepie produktu . Na przykład produktowej suwaka , produktowej liście i product-wyszukiwania komponentów.

Fałszywy klient produktu

Oto fałszywy klient, który możesz zastąpić wywoływaniem rzeczywistych produktów zwracających punkty końcowe.

var ProductClient = {

  load: function(success, failure) {
    setTimeout(function() {
      var ITEMS = require('../data/product-data.js');
      success(ITEMS);
    }, 1000);
  }    
};

module.exports = ProductClient;

Sklep z produktami

Oto sklep z produktami, oczywiście jest to bardzo minimalny sklep.

var Fluxxor = require("fluxxor");

var store = Fluxxor.createStore({

  initialize: function(options) {

    this.productItems = [];

    this.bindActions(
      constants.LOAD_PRODUCTS_SUCCESS, this.onLoadSuccess,
      constants.LOAD_PRODUCTS_FAIL, this.onLoadFail
    );
  },

  onLoadSuccess: function(data) {    
    for(var i = 0; i < data.products.length; i++){
      this.productItems.push(data.products[i]);
    }    
    this.emit("change");
  },

  onLoadFail: function(error) {
    console.log(error);    
    this.emit("change");
  },    

  getState: function() {
    return {
      productItems: this.productItems
    };
  }
});

module.exports = store;

Teraz akcje produktu, które powodują żądanie AJAX i po pomyślnym uruchomieniu uruchamiają akcję LOAD_PRODUCTS_SUCCESS zwracającą produkty do sklepu.

Akcje produktu

var ProductClient = require("../fake-clients/product-client");

var actions = {

  loadProducts: function() {

    ProductClient.load(function(products) {
      this.dispatch(constants.LOAD_PRODUCTS_SUCCESS, {products: products});
    }.bind(this), function(error) {
      this.dispatch(constants.LOAD_PRODUCTS_FAIL, {error: error});
    }.bind(this));
  }    

};

module.exports = actions;

Więc dzwonię this.getFlux().actions.productActions.loadProducts() z dowolnego komponentu słuchającego tego sklepu spowoduje załadowanie produktów.

Można sobie wyobrazić różne działania, które reagowałyby na interakcje użytkownika, takie jak addProduct(id) removeProduct(id)itp. ... według tego samego wzorca.

Mam nadzieję, że ten przykład trochę pomoże, ponieważ uznałem, że jest to trochę trudne do wdrożenia, ale z pewnością pomogło utrzymać 100% synchronizację moich sklepów.

svnm
źródło
2

Odpowiedziałem na pokrewne pytanie tutaj: jak obsługiwać zagnieżdżone wywołania interfejsu API w trybie ciągłym

Działania nie powinny być rzeczami powodującymi zmianę. Mają być jak gazeta, która informuje o zastosowaniu zmiany w świecie zewnętrznym, a następnie aplikacja odpowiada na te wiadomości. Sklepy same w sobie powodują zmiany. Działania tylko je informują.

Bill Fisher, twórca Flux https://stackoverflow.com/a/26581808/4258088

Zasadniczo powinieneś robić, określając za pomocą działań, jakie dane potrzebujesz. Jeśli sklep zostanie poinformowany o działaniu, powinien zdecydować, czy musi pobrać jakieś dane.

Sklep powinien być odpowiedzialny za gromadzenie / pobieranie wszystkich potrzebnych danych. Należy jednak pamiętać, że po tym, jak sklep zażądał danych i otrzymał odpowiedź, powinien sam wywołać akcję z pobranymi danymi, w przeciwieństwie do bezpośredniego przetwarzania / zapisywania odpowiedzi przez sklep.

Sklepy mogą wyglądać mniej więcej tak:

class DataStore {
  constructor() {
    this.data = [];

    this.bindListeners({
      handleDataNeeded: Action.DATA_NEEDED,
      handleNewData: Action.NEW_DATA
    });
  }

  handleDataNeeded(id) {
    if(neededDataNotThereYet){
      api.data.fetch(id, (err, res) => {
        //Code
        if(success){
          Action.newData(payLoad);
        }
      }
    }
  }

  handleNewData(data) {
    //code that saves data and emit change
  }
}
MoeSattler
źródło
0

Oto moje zdanie na ten temat: http://www.thedreaming.org/2015/03/14/react-ajax/

Mam nadzieję, że to pomaga. :)

Jason Walton
źródło
8
głosuj zgodnie z wytycznymi. umieszczanie odpowiedzi w witrynach zewnętrznych sprawia, że ​​ta witryna jest mniej przydatna, i powoduje, że odpowiedzi są gorszej jakości, co obniża użyteczność witryny. zewnętrzne adresy URL prawdopodobnie również z czasem się zepsują. opinia nie mówi nic o przydatności artykułu, który, nawiasem mówiąc, jest bardzo dobry :)
oligofren
2
Dobry post, ale dodanie krótkiego podsumowania zalet / wad każdego podejścia zapewni Ci pozytywne głosy. W przypadku SO nie powinniśmy klikać łącza, aby uzyskać sedno swojej odpowiedzi.
Cory House,