Dlaczego potrzebujemy middleware dla asynchronicznego przepływu w Redux?

Zgodnie z dokumentami, "bez oprogramowania pośredniczącego, Redux store obsługuje tylko synchroniczny przepływ danych". Nie rozumiem, dlaczego tak jest. Dlaczego komponent kontenera nie może wywołać asynchronicznego API, a następnie dispatch Akcji?

Wyobraź sobie na przykład prosty interfejs użytkownika: pole i przycisk. Gdy użytkownik naciśnie przycisk, pole zostanie wypełnione danymi ze zdalnego serwera.

Pole i przycisk

import * as React from 'react';
import * as Redux from 'redux';
import { Provider, connect } from 'react-redux';

const ActionTypes = {
    STARTED_UPDATING: 'STARTED_UPDATING',
    UPDATED: 'UPDATED'
};

class AsyncApi {
    static getFieldValue() {
        const promise = new Promise((resolve) => {
            setTimeout(() => {
                resolve(Math.floor(Math.random() * 100));
            }, 1000);
        });
        return promise;
    }
}

class App extends React.Component {
    render() {
        return (
            <div>
                <input value={this.props.field}/>
                <button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
                {this.props.isWaiting && <div>Waiting...</div>}
            </div>
        );
    }
}
App.propTypes = {
    dispatch: React.PropTypes.func,
    field: React.PropTypes.any,
    isWaiting: React.PropTypes.bool
};

const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
    switch (action.type) {
        case ActionTypes.STARTED_UPDATING:
            return { ...state, isWaiting: true };
        case ActionTypes.UPDATED:
            return { ...state, isWaiting: false, field: action.payload };
        default:
            return state;
    }
};
const store = Redux.createStore(reducer);
const ConnectedApp = connect(
    (state) => {
        return { ...state };
    },
    (dispatch) => {
        return {
            update: () => {
                dispatch({
                    type: ActionTypes.STARTED_UPDATING
                });
                AsyncApi.getFieldValue()
                    .then(result => dispatch({
                        type: ActionTypes.UPDATED,
                        payload: result
                    }));
            }
        };
    })(App);
export default class extends React.Component {
    render() {
        return <Provider store={store}><ConnectedApp/></Provider>;
    }
}

Kiedy wyeksportowany komponent jest renderowany, mogę kliknąć przycisk i wejście jest aktualizowane poprawnie.

Zwróć uwagę na funkcję update w wywołaniu connect. Wysyła akcję informującą aplikację o aktualizacji, a następnie wykonuje asynchroniczne wywołanie. Po zakończeniu wywołania dostarczona wartość jest wysyłana jako ładunek innej akcji.

Co jest nie tak z tym podejściem? Dlaczego miałbym używać Redux Thunk lub Redux Promise, jak sugeruje dokumentacja?

EDIT: przeszukałem repo Redux w poszukiwaniu wskazówek i znalazłem, że Twórcy akcji w przeszłości musieli być czystymi funkcjami. Na przykład, oto użytkownik próbuje zapewnić lepsze wyjaśnienie asynchronicznego przepływu danych:

Sam twórca akcji nadal jest czystą funkcją, ale funkcja thunk, którą zwraca, nie musi nią być i może wykonywać nasze wywołania asynchroniczne]}

Twórcy akcji nie muszą już być czyści. więc, thunk/promise middleware było zdecydowanie wymagane w przeszłości, ale wydaje się, że nie jest to dłuższa sprawa?

Author: Qwerty, 2016-01-03

7 answers

Co jest nie tak z tym podejściem? Dlaczego miałbym używać Redux Thunk lub Redux Promise, jak sugeruje dokumentacja?

Nie ma nic złego w tym podejściu. Jest to po prostu niewygodne w dużej aplikacji, ponieważ będziesz miał różne komponenty wykonujące te same akcje, możesz chcieć usunąć niektóre akcje Lub zachować jakiś stan lokalny, taki jak Auto-incrementing ID blisko twórców akcji itp. Więc jest to po prostu łatwiejsze z punktu widzenia konserwacji do wyodrębnij twórców akcji do osobnych funkcji.

Możesz przeczytać moją odpowiedź na "jak wysłać akcję Redux z limitem czasu" , aby uzyskać bardziej szczegółowy opis.

Middleware takie jak Redux Thunk lub Redux Promise daje Ci "cukier składniowy" do wysyłania thunksów lub obietnic, ale nie musisz[24]}używać go.

Więc, bez żadnego oprogramowania pośredniczącego, Twój twórca akcji może wyglądać jak

// action creator
function loadData(dispatch, userId) { // needs to dispatch, so it is first argument
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
}

Ale z Thunk Middleware możesz to napisać Tak:

// action creator
function loadData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do
}

Więc nie ma wielkiej różnicy. Jedną z rzeczy, które lubię w tym drugim podejściu, jest to, że komponent nie obchodzi, że twórca akcji jest asynchroniczny. Po prostu wywołuje dispatch normalnie, może również użyć mapDispatchToProps, aby powiązać takiego twórcę akcji z krótką składnią,itp. Komponenty nie wiedzą, w jaki sposób twórcy akcji są zaimplementowani i można przełączać się między różnymi podejściami asynchronicznymi (Redux Thunk, Redux Promise, Redux Saga) bez zmiany komponentów. Z drugiej strony, z pierwsze, jawne podejście, Twoje komponenty wiedzą dokładnie , że określone wywołanie jest asynchroniczne i musi dispatch być przekazywane przez jakąś konwencję (na przykład jako parametr synchronizacji).

Zastanów się także, jak zmieni się ten kod. Powiedzmy, że chcemy mieć drugą funkcję ładowania danych i połączyć je w jeden Kreator akcji.

Przy pierwszym podejściu musimy być świadomi, jakiego rodzaju działania nazywamy twórcą:

// action creators
function loadSomeData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(dispatch, userId) {
  return Promise.all(
    loadSomeData(dispatch, userId), // pass dispatch first: it's async
    loadOtherData(dispatch, userId) // pass dispatch first: it's async
  );
}


// component
componentWillMount() {
  loadAllData(this.props.dispatch, this.props.userId); // pass dispatch first
}

Z Redux Thunk action twórcy mogą dispatch wynik innych twórców akcji i nawet nie myśleć, czy są synchroniczne czy asynchroniczne: {]}

// action creators
function loadSomeData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(userId) {
  return dispatch => Promise.all(
    dispatch(loadSomeData(userId)), // just dispatch normally!
    dispatch(loadOtherData(userId)) // just dispatch normally!
  );
}


// component
componentWillMount() {
  this.props.dispatch(loadAllData(this.props.userId)); // just dispatch normally!
}

Przy takim podejściu, jeśli później chcesz, aby twórcy akcji przyjrzeli się aktualnemu stanowi Redux, możesz po prostu użyć drugiego argumentugetState przekazanego do thunksa bez modyfikowania kodu wywołującego:

function loadSomeData(userId) {
  // Thanks to Redux Thunk I can use getState() here without changing callers
  return (dispatch, getState) => {
    if (getState().data[userId].isLoaded) {
      return Promise.resolve();
    }

    fetch(`http://data.com/${userId}`)
      .then(res => res.json())
      .then(
        data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
        err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
      );
  }
}

Jeśli musisz zmienić go, aby był synchroniczny, możesz to zrobić również bez zmiany kodu wywołującego:

// I can change it to be a regular action creator without touching callers
function loadSomeData(userId) {
  return {
    type: 'LOAD_SOME_DATA_SUCCESS',
    data: localStorage.getItem('my-data')
  }
}

Więc korzyść używanie oprogramowania pośredniczącego takiego jak Redux Thunk lub Redux Promise polega na tym, że komponenty nie są świadome tego, jak twórcy akcji są implementowani, czy dbają o stan Redux, czy są synchroniczne czy asynchroniczne i czy wzywają innych twórców akcji. Minusem jest trochę intymności, ale uważamy, że warto w rzeczywistych zastosowaniach.

Wreszcie, Redux Thunk i przyjaciele to tylko jedno możliwe podejście do asynchronicznych żądań w aplikacjach Redux. Kolejne ciekawe podejście to Redux Saga, która pozwala zdefiniować długo działające demony ("Saga"), które podejmują działania w miarę ich nadejścia i przekształcają lub wykonują żądania przed wystąpieniem akcji. To przenosi logikę z twórców akcji do sagi. Możesz to sprawdzić, a później wybrać to, co najbardziej Ci odpowiada.

Przeszukałem repo Redux w poszukiwaniu wskazówek i odkryłem, że twórcy akcji musieli być czystymi funkcjami w przeszłości.

To jest niepoprawne. Lekarze powiedzieli to, ale lekarze się mylili.
Twórcy akcji nigdy nie musieli być czystymi funkcjami.
Naprawiliśmy dokumenty, żeby to odzwierciedlić.

 449
Author: Dan Abramov,
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-05-23 12:02:59

Nie wiesz.]} Ale... powinieneś użyć redux-saga:)

Dan Abramov ' s answer is right about redux-thunk but I will talk a bit more about redux-saga that is quite similar but more powerful.

Imperatyw VS deklaratywny

  • DOM : jQuery jest imperatywny / React jest deklaratywny
  • monady : IO jest imperatywne / wolne jest deklaratywne
  • efekty Redux: redux-thunk is imperatyw / redux-saga is deklaratywne

Kiedy masz thunk w swoich rękach, jak monada IO lub obietnica, nie możesz łatwo wiedzieć, co zrobi, gdy wykonasz. Jedynym sposobem na przetestowanie thunka jest jego wykonanie i wyśmiewanie dyspozytora (lub całego świata zewnętrznego, jeśli wchodzi w interakcję z większą ilością rzeczy...).

Jeśli używasz mocks, to nie robisz programowania funkcyjnego.

Widziane przez pryzmat efektów ubocznych, szyderstwa są flagą, że Twój kod jest nieczysty, a w funkcjonalne oko programisty, dowód na to, że coś jest nie tak. Zamiast ściągać bibliotekę, która pomoże nam sprawdzić, czy góra lodowa jest nienaruszona, powinniśmy ją opłynąć. Hardcorowy facet z TDD / Java zapytał mnie kiedyś, jak robisz kpiny w Clojure. Zazwyczaj widzimy to jako znak, że musimy przeformułować nasz kod.

Źródło

Sagi (ponieważ zostały zaimplementowane w redux-saga) są deklaratywne i podobnie jak wolna monada czy komponenty Reactowe, są znacznie łatwiejsze do przetestowania bez makiety.

Zobacz też ten Artykuł :

We współczesnym FP nie powinniśmy pisać programów - powinniśmy pisać opisy programów, które możemy następnie introspektować, przekształcać i interpretować do woli.

W rzeczywistości Redux-saga jest jak hybryda: przepływ jest imperatywny, ale efekty są deklaratywne.]}

Zamieszanie: akcje/zdarzenia/polecenia...

W świecie frontend jest wiele zamieszania na temat tego, jak niektóre pojęcia backendowe, takie jak CQRS / EventSourcing i Flux / Redux mogą być ze sobą powiązane, głównie dlatego, że w Flux używamy terminu "akcja", który czasami może reprezentować zarówno imperatywny kod (LOAD_USER), jak i zdarzenia (USER_LOADED). Uważam, że podobnie jak zaopatrzenie w wydarzenia, należy wysyłać tylko wydarzenia.

Używanie sag w praktyce

Wyobraź sobie aplikację z linkiem do profilu użytkownika. Idiomatyczny sposób radzenia sobie z tym z obydwoma pośrednimi be:

redux-thunk

<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>

function loadUserProfile(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
      err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
    );
}

redux-saga

<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>


function* loadUserProfileOnNameClick() {
  yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}

function* fetchUser(action) {
  try {
    const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
    yield put({ type: 'USER_PROFILE_LOADED', userProfile })
  } 
  catch(err) {
    yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
  }
}

Ta saga tłumaczy się na:

Za każdym razem, gdy użytkownik zostanie kliknięty, Pobierz profil użytkownika, a następnie wyślij Zdarzenie z załadowanym profilem.

Jak widzisz, są pewne zalety redux-saga.

Użycie takeLatest pozwala wyrazić, że jesteś zainteresowany tylko danymi ostatniego kliknięcia nazwy użytkownika (rozwiązywanie problemów z współbieżnością w przypadku, gdy użytkownik kliknie bardzo szybko na wiele nazwy użytkownika). Takie rzeczy są trudne z thunksem. Mogłeś użyć takeEvery, Jeśli nie chcesz tego zachowania.

Utrzymujesz twórców akcji w czystości. Uwaga nadal warto przechowywać actionCreators (w sagach put i komponentach dispatch), ponieważ może to pomóc w dodaniu walidacji akcji (assertions/flow/typescript) w przyszłości.

Twój kod staje się znacznie bardziej testowalny, ponieważ efekty są deklaratywne

Nie musisz już uruchamiać połączeń podobnych do rpc, takich jak actions.loadUser(). Twój Interfejs musi tylko wysłać to, co się stało. Odpalamy tylko zdarzenia (zawsze w czasie przeszłym!) i już nie działa. Oznacza to, że można tworzyć odsprzęgnięte "kaczki" lub ograniczone konteksty i że saga może działać jako punkt sprzężenia między tymi modułowymi komponentami.

Oznacza to, że Twoje widoki są łatwiejsze do zarządzania, ponieważ nie muszą już zawierać warstwy tłumaczenia między tym, co się stało, a tym, co powinno się wydarzyć jako efekt

Na przykład wyobraź sobie nieskończony widok przewijania. CONTAINER_SCROLLED może prowadzić do NEXT_PAGE_LOADED, ale czy to naprawdę odpowiedzialność przewijalnego kontenera, aby zdecydować, czy powinniśmy załadować kolejną stronę? Następnie musi być świadomy bardziej skomplikowanych rzeczy, takich jak czy Ostatnia strona została załadowana pomyślnie lub czy istnieje już strona, która próbuje załadować, czy nie ma już elementów do załadowania? Nie sądzę: dla maksymalnej możliwości ponownego użycia, przewijalny kontener powinien po prostu opisać to został przewinięty. W tym celu należy kliknąć na przycisk "Zapisz się" i kliknąć przycisk "Zapisz się".]}

Niektórzy mogą twierdzić, że generatory mogą z natury ukrywać stan poza magazynem redux za pomocą zmiennych lokalnych, ale jeśli zaczniesz organizować złożone rzeczy wewnątrz thunks przez uruchamianie timerów itp, i tak będziesz miał ten sam problem. I jest efekt select, który teraz pozwala uzyskać jakiś stan z twojego sklepu Redux.

Sagi mogą być podróżowane w czasie, a także umożliwiają złożone rejestrowanie przepływu i dev-narzędzia, nad którymi aktualnie pracujemy. Oto kilka prostych zapisów przepływu asynchronicznego, które są już zaimplementowane:

Saga flow logging

Odsprzęganie

Sagi nie tylko zastępują Redux thunks. Pochodzą one z backendu / systemów rozproszonych / event-sourcing.

Jest to bardzo powszechne błędne przekonanie, że sagi są tutaj po to, aby zastąpić Twoje Redux thunks lepszą testowalnością. W rzeczywistości jest to tylko szczegóły realizacji redux-saga. Użycie deklaratywne efekty są lepsze niż thunks dla testowalności, ale wzór sagi może być zaimplementowany na kodzie imperatywnym lub deklaratywnym.

[29]} Po pierwsze, Saga jest oprogramowaniem, które pozwala koordynować długotrwałe transakcje (ewentualna spójność) i transakcje w różnych ograniczonych kontekstach (żargon domain driven design).

Aby uprościć to dla świata frontend, wyobraź sobie, że istnieją widget1 i widget2. Po kliknięciu jakiegoś przycisku na widget1 należy mają wpływ na widget2. Widget1 wysyła akcję, która dotyczy widget2), widget1 wysyła tylko to, że jego przycisk został kliknięty. Następnie Saga posłuchaj tego przycisku Kliknij, a następnie zaktualizuj widget2, usuwając nowe zdarzenie, które widget2 jest świadomy.

Dodaje to poziom indrection, który jest niepotrzebny dla prostych aplikacji, ale ułatwia skalowanie złożonych aplikacji. Możesz teraz publikować widget1 i widget2 do różnych npm repozytoria, aby nigdy nie musiały się o sobie nawzajem wiedzieć, bez konieczności udostępniania globalnego rejestru działań. Widżety 2 są teraz ograniczone konteksty, które mogą żyć oddzielnie. Nie muszą być spójne i mogą być ponownie wykorzystane w innych aplikacjach. Saga jest punktem połączenia między dwoma widżetami, które koordynują je w znaczący sposób dla Twojej firmy.

Kilka ciekawych artykułów na temat struktury aplikacji Redux, na której możesz użyć Redux-saga dla przyczyny rozłączenia:

Konkretny sposób użycia: system powiadomień

Chcę, aby moje komponenty mogły wyzwalać wyświetlanie powiadomień w aplikacji. Ale nie chcę, aby moje komponenty były mocno sprzężone z system powiadomień, który ma własne reguły biznesowe (maksymalnie 3 powiadomienia wyświetlane w tym samym czasie, kolejkowanie powiadomień, czas wyświetlania 4 sekund itp...).

Nie chcę, aby moje komponenty JSX decydowały, kiedy powiadomienie zostanie wyświetlone / Ukryte. Po prostu daję mu możliwość zażądania powiadomienia, i zostawić złożone zasady wewnątrz sagi. Tego rodzaju rzeczy są dość trudne do wdrożenia z thunks lub obietnic.

powiadomienia

Opisałem tutaj jak można to zrobić z saga

Dlaczego nazywa się to Saga?

Termin saga pochodzi ze świata zaplecza. Początkowo wprowadziłem Yassine ' a (autora Redux-sagi) do tego terminu w długiej dyskusji {31]}.

Początkowo termin ten został wprowadzony za pomocą papieru , wzór Saga miał być używany do obsługi ewentualnej spójności w transakcjach rozproszonych, ale jego użycie zostało rozszerzone do szerszej definicji przez backend deweloperzy tak, że teraz obejmuje również wzór" process manager " (jakoś oryginalny wzór Saga jest wyspecjalizowaną formą menedżera procesów).

Dzisiaj termin "saga" jest mylący, ponieważ może opisywać 2 różne rzeczy. Ponieważ jest on używany w redux-saga, nie opisuje sposobu obsługi transakcji rozproszonych, ale raczej sposób koordynowania działań w aplikacji. redux-saga mógł być również nazywany redux-process-manager.

Zobacz też:

Alternatywy

Jeśli nie podoba Ci się pomysł użycia generatorów, ale interesuje Cię wzór saga i jego właściwości odsprzęgające, możesz również osiągnąć to samo za pomocą redux-observable , który używa nazwy epic, aby opisać dokładnie to samo wzór, ale z RxJS. Jeśli znasz już Rx, poczujesz się jak w domu.

const loadUserProfileOnNameClickEpic = action$ =>
  action$.ofType('USER_NAME_CLICKED')
    .switchMap(action =>
      Observable.ajax(`http://data.com/${action.payload.userId}`)
        .map(userProfile => ({
          type: 'USER_PROFILE_LOADED',
          userProfile
        }))
        .catch(err => Observable.of({
          type: 'USER_PROFILE_LOAD_FAILED',
          err
        }))
    );

Niektóre przydatne zasoby redux-saga

2017 radzi

  • nie nadużywaj Redux-saga tylko ze względu na używanie to. Testowalne wywołania API nie są tego warte.
  • nie usuwaj thunksów ze swojego projektu w większości prostych przypadków.
  • Nie wahaj się wysłać thunksa w yield put(someActionThunk) jeśli to ma sens.

Jeśli obawiasz się używania Redux - Saga (lub Redux-observable), ale potrzebujesz tylko schematu oddzielającego, sprawdź redux-dispatch-subscribe: pozwala na odsłuchiwanie wysyłek i wyzwalanie nowych wysyłek w listenerze.

const unsubscribe = store.addDispatchListener(action => {
  if (action.type === 'ping') {
    store.dispatch({ type: 'pong' });
  }
});
 355
Author: Sebastien Lorber,
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-10-19 15:19:48

Krótka odpowiedź : wydaje mi się całkowicie rozsądne podejście do problemu asynchronicznego. Z kilkoma zastrzeżeniami.

Miałem bardzo podobny pogląd podczas pracy nad nowym projektem, który właśnie rozpoczęliśmy w mojej pracy. Byłem wielkim fanem eleganckiego systemu vanilla Redux do aktualizacji sklepu i ponownej dystrybucji komponentów w sposób, który pozostaje poza wnętrznościami drzewa komponentów Reactowych. Wydawało mi się dziwne, aby podłączyć się do tego eleganckiego dispatch mechanizmu do obsługi asynchroniczne.

Skończyłem z bardzo podobnym podejściem do tego, co masz w bibliotece, którą uwzględniłem w naszym projekcie, który nazwaliśmy react-redux-controller .

Skończyło się na tym, że nie wybrałem dokładnego podejścia, które masz powyżej z kilku powodów:

  1. sposób w jaki to napisałeś, te funkcje dyspozytorskie nie mają dostępu do sklepu. Możesz to jakoś obejść, mając komponenty interfejsu użytkownika przekazujące wszystkie informacje o wysyłaniu potrzeby funkcji. Ale argumentowałbym, że to niepotrzebnie łączy te komponenty interfejsu z logiką wysyłania. Co bardziej problematyczne, funkcja dyspozytorska nie ma oczywistego sposobu, aby uzyskać dostęp do zaktualizowanego stanu w kontynuacjach asynchronicznych.
  2. funkcje dyspozytorskie mają dostęp do samej dispatch poprzez zakres leksykalny. Ogranicza to możliwości refaktoryzacji, gdy tylko to connect wymknie się spod kontroli-i wygląda to dość nieporęcznie z tą tylko jedną metodą update. Więc potrzebujesz jakiegoś systemu do pozwala Ci komponować te funkcje dyspozytora, jeśli podzielisz je na oddzielne moduły.

Weź razem, musisz skonfigurować jakiś system, aby umożliwić {[0] } i wstrzyknięcie sklepu do funkcji dyspozytorskich, wraz z parametrami zdarzenia. Znam trzy rozsądne podejścia do tej zależności:

  • Redux-thunk robi to w sposób funkcjonalny, przekazując je do swoich thunksów (czyniąc je nie do końca thunksami, przez kopułę definicje). Nie pracowałem z innymi dispatch podejściami middleware, ale zakładam, że są w zasadzie takie same.
  • React-redux-controller robi to za pomocą coroutine. Jako bonus, daje również dostęp do" selektorów", które są funkcjami, które mogłeś przekazać jako pierwszy argument connect, zamiast pracować bezpośrednio z surowym, znormalizowanym magazynem.
  • można również zrobić to w sposób obiektowy, wstrzykując je do kontekstu this, poprzez różnorodność możliwych mechanizmów.

Update

Wydaje mi się, że częścią tej zagadki jest ograniczenie react-redux. Pierwszy argument do connect dostaje migawkę stanu, ale nie wysyłkę. Drugi argument dostaje dispatch, ale nie Stan. Żaden argument nie otrzymuje thunk, który zamyka się nad bieżącym stanem, ponieważ jest w stanie zobaczyć zaktualizowany stan w czasie kontynuacji/wywołania zwrotnego.

 24
Author: acjay,
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-01-04 13:35:00

Celem Abramova - i każdego idealnie-jest po prostu hermetyzacja złożoności (i asynchronicznych wywołań) w miejscu, w którym jest to najbardziej odpowiednie.

Gdzie najlepiej to zrobić w standardowym Redux dataflow? A może:

  • Reduktory ? Nie ma mowy. Powinny to być czyste funkcje bez skutków ubocznych. Aktualizacja sklepu to poważna, skomplikowana sprawa. Nie zanieczyszczaj go.
  • Dumb View Components?Zdecydowanie Nie. Mają jeden problem: prezentacji i interakcji z użytkownikiem, i powinny być tak proste, jak to możliwe.
  • Elementy Kontenera?Możliwe, ale nieoptymalne. Ma to sens w tym, że kontener jest miejscem, w którym hermetyzujemy pewną złożoność związaną z widokiem i wchodzimy w interakcję ze sklepem, ale:
    • kontenery muszą być bardziej złożone niż głupie komponenty, ale to nadal jedna odpowiedzialność: zapewnienie powiązań między view i state / store. Twoja logika asynchroniczna jest zupełnie oddzielna od to.
    • umieszczając go w kontenerze, blokujesz logikę asynchroniczną w jednym kontekście, dla pojedynczego widoku/trasy. Zły pomysł. Idealnie jest to wszystko wielokrotnego użytku i całkowicie odsprzęgnięte.
  • S ome inny moduł serwisowy? zły pomysł: musisz wprowadzić dostęp do sklepu, co jest koszmarem konserwacji/testowania. Lepiej iść z ziarnem Redux i uzyskać dostęp do sklepu tylko za pomocą dostarczonych interfejsów API/modeli.
  • działania i środki, które interpretować?Dlaczego nie?! Na początek, to jedyna ważna opcja, jaką nam pozostało. :- ) Bardziej logicznie, System akcji jest odsprzęgnięta logika wykonania, że można użyć z dowolnego miejsca. Ma dostęp do sklepu i może wysyłać więcej akcji. Ma jedną odpowiedzialność, która polega na zorganizowaniu przepływu kontroli i danych wokół aplikacji, a większość asynchronicznych pasuje do tego.
    • a co z twórcami akcji? Dlaczego nie po prostu zrobić asynchroniczne tam, zamiast w działaniach siebie i w Middleware?
      • Po pierwsze i najważniejsze, twórcy nie mają dostępu do sklepu, tak jak middleware. Oznacza to, że nie możesz wysyłać nowych akcji warunkowych, nie możesz czytać ze sklepu, aby skomponować swoją asynchronikę itp.
      • Więc zachowaj złożoność w miejscu, które jest złożone z konieczności, a Wszystko inne będzie proste. Twórcy mogą wtedy być proste, stosunkowo czyste funkcje, które są łatwe do przetestowania.
 13
Author: XML,
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-30 13:13:27

Aby odpowiedzieć na pytanie zadane na początku:

Dlaczego komponent kontenera nie może wywołać API asynchronicznego, a następnie wysłać akcji?

Należy pamiętać, że te dokumenty są dla Redux, a nie Redux plus React. Redux stores podłączony do komponentów Reactowych może robić dokładnie to, co mówisz, ale zwykły sklep Jane Redux bez oprogramowania pośredniczącego nie akceptuje argumentów do dispatch z wyjątkiem zwykłych starych obiektów.

Bez middleware można oczywiście still do

const store = createStore(reducer);
MyAPI.doThing().then(resp => store.dispatch(...));

Ale jest to podobny przypadek, gdy asynchronika jest owinięta wokół Redux, a nie obsługiwana przez Redux. Tak więc middleware pozwala na asynchroniczne modyfikowanie tego, co można przekazać bezpośrednio do dispatch.


To powiedziawszy, duch Twojej sugestii jest, myślę, słuszny. Z pewnością istnieją inne sposoby na asynchroniczną obsługę aplikacji Redux + React.

Jedną z zalet korzystania z middleware jest to, że możesz nadal używać action twórcy jako normalni, nie martwiąc się dokładnie, jak są podłączeni. Na przykład, używając redux-thunk, kod, który napisałeś, wyglądałby podobnie do

function updateThing() {
  return dispatch => {
    dispatch({
      type: ActionTypes.STARTED_UPDATING
    });
    AsyncApi.getFieldValue()
      .then(result => dispatch({
        type: ActionTypes.UPDATED,
        payload: result
      }));
  }
}

const ConnectedApp = connect(
  (state) => { ...state },
  { update: updateThing }
)(App);

, który nie wygląda tak różnie od oryginału - jest tylko trochę przetasowany - i connect nie wie, że updateThing jest (lub musi być) asynchroniczny.

Jeśli chcesz również wspierać obietnice, obserwowalne, sagi , lub szalone zwyczaje I wysoce deklaratywne działanie twórcy, a następnie Redux może to zrobić po prostu zmieniając to, co PRZEKAZUJESZ do dispatch (aka, co zwracasz od twórców akcji). Nie ma potrzeby muckingu z komponentami Reactowymi (lub wywołaniami connect).

 9
Author: Michelle Tilley,
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-01-04 06:20:14

Ok, najpierw zobaczmy, jak działa middleware, Co odpowiada na pytanie, jest to funkcja kodu źródłowego applyMiddleWare w Redux:

function applyMiddleware() {
  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function (reducer, preloadedState, enhancer) {
      var store = createStore(reducer, preloadedState, enhancer);
      var _dispatch = store.dispatch;
      var chain = [];

      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch(action) {
          return _dispatch(action);
        }
      };
      chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      _dispatch = compose.apply(undefined, chain)(store.dispatch);

      return _extends({}, store, {
        dispatch: _dispatch
      });
    };
  };
}

Spójrz na tę część, zobacz jak nasza staje się funkcją .

  ...
  getState: store.getState,
  dispatch: function dispatch(action) {
  return _dispatch(action);
}
  • zauważ, że każda warstwa pośrednicząca otrzyma funkcje dispatch i getState jako nazwane argumenty.

OK, tak wygląda Redux-thunk jako jeden z najczęściej używanych middlewares dla Redux :

Redux Thunk middleware pozwala na pisanie twórców akcji, którzy zwracają funkcja zamiast akcji. Thunk może być użyty do opóźnienia wysłania czynności, lub do wysłania tylko wtedy, gdy określony warunek jest met. Wewnętrzna funkcja odbiera metody przechowywania i getState jako parametry.

Więc jak widzisz, zwróci ona funkcję zamiast akcji, co oznacza, że możesz poczekać i wywołać ją w dowolnym momencie, ponieważ jest to funkcja...

Więc co co to jest thunk? Tak to jest wprowadzone w Wikipedii:

W programowaniu komputerowym thunk jest podprogramem używanym do wstrzykiwania dodatkowe obliczenia do innego podprogramu. Thunks to przede wszystkim służy do opóźniania obliczeń do czasu ich potrzeby lub do wstawienia operacje na początku lub końcu drugiego podprogramu. Mają wiele innych aplikacji do generowania kodu kompilatora i w programowanie modułowe.

Termin powstał jako żartobliwy pochodna "myśleć".

Thunk to funkcja, która zawija wyrażenie, aby opóźnić jego ocena.

//calculation of 1 + 2 is immediate 
//x === 3 
let x = 1 + 2;

//calculation of 1 + 2 is delayed 
//foo can be called later to perform the calculation 
//foo is a thunk! 
let foo = () => 1 + 2;

Przekonaj się więc, jak prosta jest ta koncepcja i jak może pomóc w zarządzaniu akcjami asynchronicznymi...

To coś, bez czego można żyć, ale pamiętaj, że w programowaniu zawsze są lepsze, schludniejsze i właściwe sposoby na robienie rzeczy...

Zastosuj middleware Redux

 3
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
2017-11-19 04:50:15

Używanie Redux-saga jest najlepszym oprogramowaniem pośredniczącym w implementacji Reacta-redux.

Ex: sklep.js

  import createSagaMiddleware from 'redux-saga';
  import { createStore, applyMiddleware } from 'redux';
  import allReducer from '../reducer/allReducer';
  import rootSaga from '../saga';

  const sagaMiddleware = createSagaMiddleware();
  const store = createStore(
     allReducer,
     applyMiddleware(sagaMiddleware)
   )

   sagaMiddleware.run(rootSaga);

 export default store;

A potem saga.js

import {takeLatest,delay} from 'redux-saga';
import {call, put, take, select} from 'redux-saga/effects';
import { push } from 'react-router-redux';
import data from './data.json';

export function* updateLesson(){
   try{
       yield put({type:'INITIAL_DATA',payload:data}) // initial data from json
       yield* takeLatest('UPDATE_DETAIL',updateDetail) // listen to your action.js 
   }
   catch(e){
      console.log("error",e)
     }
  }

export function* updateDetail(action) {
  try{
       //To write store update details
   }  
    catch(e){
       console.log("error",e)
    } 
 }

export default function* rootSaga(){
    yield [
        updateLesson()
       ]
    }

A potem akcja.js

 export default function updateFruit(props,fruit) {
    return (
       {
         type:"UPDATE_DETAIL",
         payload:fruit,
         props:props
       }
     )
  }

A następnie reduktor.js

import {combineReducers} from 'redux';

const fetchInitialData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
 const updateDetailsData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
const allReducers =combineReducers({
   data:fetchInitialData,
   updateDetailsData
 })
export default allReducers; 

A potem main.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app/components/App.jsx';
import {Provider} from 'react-redux';
import store from './app/store';
import createRoutes from './app/routes';

const initialState = {};
const store = configureStore(initialState, browserHistory);

ReactDOM.render(
       <Provider store={store}>
          <App />  /*is your Component*/
       </Provider>, 
document.getElementById('app'));
Spróbuj tego.. działa
 1
Author: sm chinna,
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-02-09 12:03:06