Kodėl mums reikalinga tarpinė programinė įranga, skirta asinchroniniam srautui Redux'e?

Remiantis dokumentais „Be tarpinės programinės įrangos,„ Redux “saugykla palaiko tik sinchroninį duomenų srautą.“ Aš nesuprantu, kodėl taip yra. Kodėl konteinerio komponentas negali skambinti asinchroniniu API ir tada dispatch veiksmus?

Pavyzdžiui, įsivaizduokite paprastą vartotojo sąsają: lauką ir mygtuką. Kai vartotojas spustelės mygtuką, laukas yra užpildytas nuotolinio serverio duomenimis.

2019

03 янв. nustatyti sbichenko 03 Jan 2016-01-03 00:09 '16 at 0:09 2016-01-03 00:09
@ 6 atsakymai

Kas yra negerai su šiuo požiūriu? Kodėl noriu naudoti „Redux Thunk“ arba „Redux Promise“, kaip rodo dokumentai?

Šiuo požiūriu nėra nieko blogo. Tai labai nepatogu didelėje programoje, nes turite skirtingų komponentų, kurie atlieka tuos pačius veiksmus, galite atsisakyti kai kurių veiksmų arba išsaugoti tam tikrą vietinę būseną, pvz., Automatinio padidinimo identifikatorius, netoli veiksmų kūrėjų ir tt Taigi, paprasčiausiai lengviau iš techninės priežiūros išskirti veiksmų kūrėjus į atskiras funkcijas.

Mano atsakymą galite perskaityti „Kaip išsiųsti„ Redux “veiksmą su„ timeout “ , kad gautumėte išsamesnį peržiūrą.

„Middleware“, pvz., „Redux Thunk“ arba „Redux Promise“, paprasčiausiai suteikia „sintaksinį cukrų“, kad išsiųstų gabalus ar pažadus, tačiau jums nereikia jo naudoti.

Taigi be jokios tarpinės programinės įrangos jūsų veiksmo kūrėjas gali atrodyti

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

Bet su „Thunk Middleware“ galite jį rašyti taip:

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

Todėl nėra daug skirtumų. Vienas dalykas, kurį man patinka apie pastarąjį požiūrį, yra tas, kad komponentas nerūpi, kad veiksmo kūrėjas yra asinchroninis. Paprastai jis paprastai dispatch , jis taip pat gali naudoti mapDispatchToProps kad susietų tokį veiksmo kūrėją su trumpa sintakse ir pan. Komponentai nežino, kaip sukuriami veiksmo kūrėjai, ir galite keisti skirtingus asinchroninius metodus (Redux Thunk, Redux Promise, Redux Saga) nekeičiant komponentų. Kita vertus, su pirmuoju, aiškiu požiūriu, jūsų komponentai tiksliai žino, kad konkretus skambutis yra asinchroninis, ir jam reikia dispatch kad būtų išsiųstas tam tikru susitarimu (pavyzdžiui, kaip sinchronizavimo parametras).

Taip pat pagalvokite, kaip pasikeis šis kodas. Tarkime, mes norime turėti antrą duomenų įkėlimo funkciją ir sujungti juos į vieną veiksmo kūrėją.

Pirmuoju požiūriu turime prisiminti, kokio veiksmo kūrėjas mes vadiname:

 // 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 async loadOtherData(dispatch, userId) // pass dispatch first: it async ); } // component componentWillMount() { loadAllData(this.props.dispatch, this.props.userId); // pass dispatch first } 

„Action Redux“ kūrėjų pagalba „Thunk“ gali dispatch kitų veiksmo kūrėjų rezultatus ir net nejausti, ar jie yra sinchroniniai ar asinchroniniai:

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

Naudodami šį metodą, jei vėliau norite, kad jūsų veiksmo kūrėjai pažvelgtų į dabartinę „Redux“ būseną, galite paprasčiausiai naudoti antrąjį getState argumentą, perduodamą į tuos vienetus, nekeičiant skambinimo kodo:

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

Jei reikia jį pakeisti į sinchroninį, galite tai padaryti ir nekeičiant skambinimo kodo:

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

Taigi, tarpinės programinės įrangos, pvz., „Redux Thunk“ arba „Redux Promise“, privalumas yra tas, kad komponentai, supažindinti su veiksmo kūrėjais, yra įgyvendinami ir ar jie rūpinasi „Redux“ būsena, ar jie yra sinchroniniai, ar asinchroniniai ir nereikalauja ar jie yra kiti veiksmo kūrėjai. Trūkumas yra nedidelis netiesioginis ryšys, bet mes manome, kad tai vertas realiose programose.

Galiausiai, „Redux Thunk“ ir draugai yra tik vienas iš galimų asinchroninių užklausų „Redux“ programų būdų. Kitas įdomus požiūris yra „ Redux Saga“ , kuri leidžia jums apibrėžti ilgai trunkančius demonus („sagas“), kurie imasi veiksmų, kai jie įvyksta, ir konvertuoti arba vykdyti prašymus prieš pradedant veiksmus. Tai perkelia veiksmo kūrėjų logiką į saga. Galite jį patikrinti ir pasirinkti, kas jums geriausiai tinka.

Aš ieškojau „Redux“ saugyklos patarimų ir nustatiau, kad veiksmų kūrėjai praeityje turėjo būti švarios funkcijos.

Tai neteisinga. Dokumentuose tai buvo pasakyta, tačiau dokumentai buvo neteisingi.
Veiksmų kūrėjams niekada nebuvo reikalaujama būti grynomis funkcijomis.
Mes nustatėme dokumentus, kad tai atspindėtų.

343
04 янв. Atsakyti Dan Abramov 04 Jan 2016-01-04 23:50 '16 at 23:50 PM 2016-01-04 23:50

Jūs neturite.

Bet ... turite naudoti labai :)

Atsakymas Danas Abramovas teisingai informuoja apie „ redux-thunk , bet aš šiek tiek daugiau pasakysiu apie „ redux-saga“ , kuri yra labai panaši, bet galingesnė.

Imperatyvus VS pareiškimas

  • DOM : reikalingas jQuery / React yra deklaratyvus
  • Monads : IO yra privalomas / Free yra deklaratyvus
  • Redux efektai : „ redux-thunk būtinai / redux-saga yra deklaratyvi

Kai jūs turite rankas scammer, pavyzdžiui, IO monadą arba pažadą, jūs negalite lengvai sužinoti, ką jis atliks po vykdymo. Vienintelis būdas patikrinti šiukšles - tai įvykdyti ir pasijuokti iš dispečerio (arba viso pasaulio, jei jis sąveikauja su daugybe medžiagų ...).

Jei naudojate triukšmą, tada neveikia funkcinis programavimas.

Matomas per šalutinį poveikį objektyvas, Mocks yra vėliava, kad jūsų kodas yra nešvarus, ir funkcinės programinės įrangos akis yra įrodymas, kad kažkas negerai. Užuot įkėlę biblioteką, kad padėtų mums patikrinti, ar yra ledkalnis, turime jį plaukti. Kietas TDD / Java vaikinas paklausė manęs, kaip jūs išgirsite Clojure. Atsakymas yra, mes paprastai ne. Paprastai tai matome kaip būtinybę reorganizuoti mūsų kodą.

Šaltinis

Sagas (įdiegtos redux-saga ) yra deklaratyvios ir, kaip ir laisvojo monado arba React komponentai, yra daug lengviau išbandyti be išdėstymo.

Taip pat žiūrėkite straipsnį :

šiuolaikinėje FP, mes neturime rašyti programų - turime parašyti programų aprašymus, kuriuos mes galime įsivaizduoti, transformuoti ir interpretuoti, kaip matome.

(Tiesą sakant, „Redux-saga“ yra panaši į hibridą: srautas yra būtinas, tačiau poveikis yra deklaracinis)

Sumišimas: veiksmai / įvykiai / komandos ...

Sąsajos pasaulyje yra daug painiavos apie tai, kaip kai kurios pagrindinės sąvokos, pvz., CQRS / EventSourcing ir Flux / Redux, gali būti susijusios, daugiausia dėl to, kad sraute mes naudojame terminą „veiksmas“, kuris kartais gali būti pateikiamas kaip privalomas kodas ( LOAD_USER ) ir įvykiai ( USER_LOADED ). Manau, kad, pavyzdžiui, renginių rengimas, turėtumėte siųsti tik įvykius.

Naudojant sagas praktikoje

Pateikite programą su nuoroda į naudotojo profilį. Idiomatiškas būdas tvarkyti tai abiem vidurkiais:

redux-thunk

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

Ši saga verčia į:

kiekvieną kartą, kai vartotojas gauna paspaudimą, suranda vartotojo profilį ir siunčia įvykį su įkeltu profiliu.

Kaip matote, yra keletas redux-saga privalumų.

Naudojant takeLatest išreikšti, kad jus domina tik gauti paskutinio vartotojo vardo duomenis (tvarkykite lygiagrečiai kylančias problemas, jei vartotojas labai greitai spusteli ant daugelio naudotojų vardų). Tokie dalykai yra sunkūs dėl avarijos. Galite naudoti „ takeEvery jei nenorite, kad šis elgesys būtų.

Sukuriate kūrėjų veiksmą švariu. Atkreipkite dėmesį, kad vis dar naudinga išsaugoti veiksmų kūrėjus (į sagas ir komponentų dispatch ), nes tai gali padėti jums ateityje pridėti patvirtinimo veiksmus (tvirtinimus / srautą / spausdintuvą).

Jūsų kodas tampa daug patikimesnis, nes poveikis yra deklaratyvus.

Jums nebereikia paleisti tipo veiksmų rpc skambučių. actions.loadUser() . Jūsų vartotojo sąsaja turėtų paprasčiausiai atsiųsti, kas vyksta. Mes fotografuojame tik įvykius (visada praeityje), o ne veiksmus. Tai reiškia, kad galite sukurti suskirstytus „ančių“ arba „ Riboto naudojimo“ kontekstus ir kad saga gali tapti šių modulinių komponentų ryšio tašku.

Tai reiškia, kad jūsų požiūriai yra lengviau valdomi, nes jiems nereikia turėti šio vertimo sluoksnio tarp to, kas įvyko ir kas turėtų įvykti kaip efektas.

Pavyzdžiui, įsivaizduokite begalinį slinkties vaizdą. CONTAINER_SCROLLED gali sukelti „ NEXT_PAGE_LOADED , bet ar iš tikrųjų yra NEXT_PAGE_LOADED konteinerio atsakomybė nuspręsti, ar įkelti kitą puslapį? Tada jis turėtų žinoti apie sudėtingesnius dalykus, pavyzdžiui, ar buvo įkeltas paskutinis puslapis, ar buvo įkeltas puslapis, arba ar buvo daugiau elementų? Aš nemanau, kad: maksimaliam pakartotiniam naudojimui, slenkamasis konteineris turėtų tiesiog apibūdinti, kad jis slenka. Puslapio įkėlimas yra šio slinkimo „verslo efektas“.

Kai kurie gali teigti, kad generatoriai gali savaime paslėpti būseną, nesusijusį su redukcija, su vietiniais kintamaisiais, bet jei pradėsite organizuoti sudėtingus dalykus viduje, veikimo laikmačiuose ir pan., Vis tiek turėsite tą pačią problemą. Ir yra select efektas, kuris dabar leidžia gauti tam tikrą būseną iš „Redux“ saugyklos.

border=0

Sagas gali keliauti per laiką ir taip pat leidžia atlikti sudėtingus srauto žurnalus ir kūrimo įrankius, kurie šiuo metu veikia. Štai keletas paprastų jau naudojamų asinchroninių protokolų:

2019

ответ дан Sebastien Lorber 06 янв. '16 в 3:43 2016-01-06 03:43

Короткий ответ : кажется мне вполне разумным подходом к проблеме асинхронности. С пару оговорок.

У меня была очень похожая мысль, когда я работала над новым проектом, который мы только начали на моей работе. Я был большим поклонником элегантной системы Vanilla Redux для обновления магазина и реиндексации компонентов таким образом, чтобы он оставался в стороне от дерева компонентов React. Мне показалось странным зацепиться за этот элегантный механизм dispatch для обработки асинхронности.

В конце концов я столкнулся с очень похожим подходом к тому, что у вас есть в библиотеке, которую я использовал в нашем проекте, которую мы назвали react-redux-controller .