redux-observable

什么是Redux辑甜?

Redux是一個了不起的庫率翅。對于那些不知道Redux是什么的人來說,它是JavaScript應(yīng)用程序的可預(yù)測狀態(tài)容器沸久。在應(yīng)用中季眷,它可以作為應(yīng)用程序狀態(tài)的單一事實來源。調(diào)用狀態(tài)或Redux_存儲只能通過調(diào)度操作進行更改卷胯,調(diào)度操作_由 reducers處理子刮,后者根據(jù)調(diào)度的操作類型決定如何修改狀態(tài)。對于那些不熟悉Redux的人窑睁,請查看此鏈接挺峡。

現(xiàn)在,Redux最常與React結(jié)合使用担钮,雖然它不受它約束 - 它可以與任何其他視圖庫一起使用橱赠。

Redux的問題

但是,Redux有一個非常重要的問題 - 它本身并不能很好地處理異步操作箫津。這很糟糕狭姨,但另一方面,Redux只是一個庫鲤嫡,為您的應(yīng)用程序提供狀態(tài)管理,就像React只是一個視圖庫一樣绑莺。這些都不構(gòu)成一個完整的框架暖眼,您必須自己選擇其他的工具。有些人認(rèn)為這是一件壞事纺裁,因為沒有一種指定的規(guī)范诫肠,包括我在內(nèi)的一些人認(rèn)為它很好,因為你不受任何特定技術(shù)的束縛欺缘。這很好栋豫,因為每個人都可以選擇他們認(rèn)為最符合他們需求的技術(shù)。

處理異步操作

現(xiàn)在谚殊,有幾個庫提供Redux中間件來處理異步操作丧鸯。當(dāng)我第一次開始使用React和Redux時,我被分配的項目使用了Redux-Thunk嫩絮。Redux-Thunk允許您編寫返回函數(shù)而不是普通對象的動作創(chuàng)建者(默認(rèn)情況下丛肢,Redux中的所有操作都必須是普通對象),這反過來又允許您延遲調(diào)度某些操作剿干。

作為當(dāng)時React / Redux的初學(xué)者蜂怎,thunk非常棒。它們易于編寫和理解置尔,并且不需要任何其他功能 - 您基本上只是以不同的方式編寫動作創(chuàng)建者杠步。

但是,一旦你開始使用React和Redux進入工作流程,你就會意識到幽歼,雖然很容易使用朵锣,但是thunks并不是那么好,因為试躏,1猪勇。你最終可能會回調(diào)地獄,特別是在發(fā)出API請求時颠蕴,2泣刹。您要么使用業(yè)務(wù)邏輯填充回調(diào)函數(shù)或減少函數(shù)來處理數(shù)據(jù)(因為,老實說犀被,您不會每次都獲得格式完美的數(shù)據(jù)椅您,特別是如果您使用第三方API),以及3.它們不是真正可測試的(你必須使用間諜方法來檢查是否已使用正確的對象調(diào)用了調(diào)度)寡键。所以掀泳,我開始研究其他可能更適合的解決方案。那是我遇到Redux-Saga的時候西轩。

Redux Saga非常接近我想要的東西员舵。你能感覺它就像一個單線程的應(yīng)用一樣,它獨自負責(zé)副作用藕畔。這基本上意味著sagas與主應(yīng)用程序分開運行并監(jiān)聽調(diào)度操作 - 一旦調(diào)度該特定saga正在偵聽的操作马僻,它就會執(zhí)行一些產(chǎn)生副作用的代碼,如API調(diào)用注服。它還允許你從saga內(nèi)部dispatch其他action韭邓,而且很容易測試,因為sagas返回效果_是普通對象溶弟。聽起來不錯女淑,對嗎?

Redux-Saga為大多數(shù)開發(fā)人員提供了折中辜御,也是一個很大的折中方案 - 它利用了Javascript的generator功能鸭你,這些功能具有相當(dāng)陡峭的學(xué)習(xí)曲線。Redux Saga創(chuàng)作者使用JS這個強大的功能擒权,但是苇本,我覺得generator功能使用起來感覺很不自然,至少對我來說菜拓,即使我知道如何他們工作以及如何使用它們瓣窄,我只是無法實際使用它們。就像那個樂隊或歌手一樣纳鼎,當(dāng)他們在收音機上播放時你聽起來并沒有什么問題俺夕,但是你甚至不會考慮自己演奏它們裳凸。這就是為什么我繼續(xù)搜索異步處理Redux中間件的原因。

Redux-Saga不能很好地處理的另一件事是取消已經(jīng)調(diào)度的異步操作 - 例如API調(diào)用(Redux Observable由于其響應(yīng)特性而做得非常好)劝贸。

下一步

大約一個星期前姨谷,我正在看一個朋友和我為大學(xué)寫過的舊Android項目,并在那里看到了一些RxJava代碼映九,并自言自語:如果有一個Redux的Reactive中間件怎么辦梦湘?所以我做了一些研究,好吧件甥,眾神聽到了我的祈禱:Cue Redux Observable捌议。

那么什么 Redux Observable?它是Redux的另一個中間件引有,它允許您以功能瓣颅,反應(yīng)和聲明的方式處理異步數(shù)據(jù)流。這是什么意思譬正?這意味著您編寫的代碼適用于異步數(shù)據(jù)流宫补。換句話說,您基本上會在這些流上監(jiān)聽新值(訂閱流*)并相應(yīng)地對這些值做出反應(yīng)曾我。

有關(guān)響應(yīng)式編程的最深入的指南粉怕,請查看此鏈接此鏈接。兩者都對(功能)響應(yīng)式編程提供了非常好的概述抒巢,并為您提供了一個非常好的入門贫贝。

Redux Observable解決了哪些問題?

在查看新的庫/工具/框架時虐秦,最重要的問題是它如何幫助您完成工作平酿。一般來說凤优,Redux Observable所做的一切悦陋,Redux-Saga也是如此。它將您的邏輯移動到您的動作創(chuàng)建者之外筑辨,它在處理異步操作方面表現(xiàn)出色俺驶,并且易于測試。然而棍辕,在我的觀點中暮现,Redux Observable的整個工作流程感覺更自然,考慮到這兩者都有一個陡峭的學(xué)習(xí)曲線(generator和響應(yīng)式編程起初有點難以掌握楚昭,因為它們不僅需要學(xué)習(xí)而且還需要學(xué)習(xí)適應(yīng)你的思維方式)栖袋。

我們現(xiàn)在可以開始編碼嗎?

所以抚太,既然你知道什么是功能性反應(yīng)式編程塘幅,如果你像我一樣昔案,你真的很喜歡操作數(shù)據(jù)的感覺。是時候?qū)⑦@個概念應(yīng)用到您的React / Redux應(yīng)用程序了电媳。

首先踏揣,作為任何Redux中間件,您必須在創(chuàng)建商店時將其添加到Redux應(yīng)用程序中匾乓。

首先捞稿,安裝它,運行
npm install --save rxjs rxjs-compat redux-observable

yarn add rxjs rxjs-compat redux-observable
取決于您正在使用的工具拼缝。

現(xiàn)在娱局,Redux Observable的基礎(chǔ)是Epics。Epics與Redux-Saga中的sagas相似珍促,不同之處在于铃辖,不是等待動作調(diào)度并將動作委托給worker,而是暫停執(zhí)行猪叙,直到使用yield關(guān)鍵字進行相同類型的另一個動作娇斩,epics分別運行并且聽取一系列動作,然后在流上收到特定動作時作出反應(yīng)穴翩。主要組件是ActionsObservableRedux-Observable犬第,它擴展了ObservableRxJS。此observable表示一系列操作芒帕,每次從應(yīng)用程序發(fā)送操作時歉嗓,都會將其添加到流中。

好的背蟆,讓我們首先創(chuàng)建我們的Redux存儲并向其添加Redux Observable中間件(小提醒鉴分,引導(dǎo)可以使用create-react-appCLI 的React項目)。在我們確定我們已經(jīng)安裝了所有依賴項(redux, react-redux, rxjs, rxjs-compat, redux-observable)后带膀,我們可以從修改我們的index.js文件開始看起來像這樣

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

import { createStore, applyMiddleware } from 'redux';
import { createEpicMiddleware } from 'redux-observable';
import { Provider } from 'react-redux';

const epicMiddleware = createEpicMiddleware(rootEpic);

const store = createStore(rootReducer, applyMiddleware(epicMiddleware));

const appWithProvider = (
    <Provider store={store}>
        <App />
    </Provider>
);

ReactDOM.render(appWithProvider, document.getElementById('root'));

你可能已經(jīng)注意到了志珍,我們錯過了rootEpicrootReducer。不要擔(dān)心這個垛叨,我們稍后會添加它們÷着矗現(xiàn)在,讓我們來看看這里發(fā)生了什么:

首先嗽元,我們正在導(dǎo)入必要的功能來創(chuàng)建我們的商店并應(yīng)用我們的中間件敛纲。在那之后,我們使用createEpicMiddlewareRedux Observable創(chuàng)建我們的中間件剂癌,并將其傳遞給根epics(我們將在稍后介紹)淤翔。然后我們使用該createStore函數(shù)創(chuàng)建我們的store并將其傳遞給root redurcer并將epics中間件應(yīng)用于store。

好的佩谷,現(xiàn)在我們已經(jīng)完成了所有設(shè)置旁壮,讓我們首先創(chuàng)建我們的root reducer辞做。創(chuàng)建一個新文件夾reducers,其中包含一個名為的新文件root.js寡具。將以下代碼添加到其中:

const initialState = {
    whiskies: [], // for this example we'll make an app that fetches and lists whiskies
    isLoading: false,
    error: false
};

export default function rootReducer(state = initialState, action) {
    switch (action.type) {
        default:
            return state;
    }
}

任何熟悉Redux的人都已經(jīng)知道這里發(fā)生了什么 - 我們正在創(chuàng)建一個reducer函數(shù)秤茅,它接受stateaction作為參數(shù),并根據(jù)動作類型返回一個新狀態(tài)(因為我們還沒有定義任何動作童叠,我們只是添加的default塊框喳,并返回初始化狀態(tài))。

現(xiàn)在厦坛,返回到您的index.js文件并添加以下導(dǎo)入:

import rootReducer from './reducers/root';

如您所見五垮,現(xiàn)在我們沒有關(guān)于rootReducer不存在的錯誤。現(xiàn)在讓我們創(chuàng)造我們的root epics; 首先杜秸,創(chuàng)建一個新文件夾epics并在其中創(chuàng)建一個名為的文件index.js放仗。在其中,暫時添加以下代碼:

import { combineEpics } from 'redux-observable';

export const rootEpic = combineEpics();

這里我們只是使用combineEpicsRedux Observable 提供的函數(shù)來組合我們的(現(xiàn)在撬碟,不存在)epics诞挨,并將該值賦給我們導(dǎo)出的常量。我們現(xiàn)在應(yīng)該index.js通過簡單地添加以下導(dǎo)入來修復(fù)條目文件中的其他錯誤:

import { rootEpic } from './epics';

不錯呢蛤,現(xiàn)在我們處理了所有配置惶傻,我們可以定義我們可以dispatch的action類型以及這些action creator。

首先其障,index.js在里面創(chuàng)建一個名為actions和一個文件的新文件夾银室。
(注意:對于大型的生產(chǎn)級的項目,您應(yīng)該以合理的方式對action励翼,reducer和epics進行分組蜈敢,而不是將它們?nèi)糠旁谝粋€文件中,但是汽抚,由于我們的應(yīng)用程序非常小抓狭,因此沒有任何意義)

在我們開始編寫代碼之前,讓我們考慮一下我們可以調(diào)度的action類型殊橙。通常辐宾,我們需要一個action來通知Redux / Redux-Observable它應(yīng)該開始獲取epics狱从,讓我們調(diào)用該動作FETCH_WHISKIES膨蛮。由于這是一個異步操作,我們不知道它究竟何時完成季研,因此我們將希望在調(diào)用成功完成時調(diào)度FETCH_WHISKIES_SUCCESS操作敞葛。以類似的方式,由于這是一個API調(diào)用与涡,它可能會失敗惹谐,我們希望通過消息通知我們的用戶持偏,因此我們將調(diào)度FETCH_WHISKIES_FAILURE操作并通過顯示錯誤消息來處理它。

讓我們在代碼中定義這些動作(及其動作創(chuàng)建者):

export const FETCH_WHISKIES = 'FETCH_WHISKYS';
export const FETCH_WHISKIES_SUCCESS = 'FETCH_WHISKYS_SUCCESS';
export const FETCH_WHISKIES_FAILURE = 'FETCH_WHISKYS_FAILURE';

export const fetchWhiskies = () => ({
    type: FETCH_WHISKIES,
});

export const fetchWhiskiesSuccess = (whiskies) => ({
    type: FETCH_WHISKIES_SUCCESS,
    payload: whiskies
});

export const fetchWhiskiesFailure = (message) => ({
    type: FETCH_WHISKIES_FAILURE,
    payload: message
});

對于那些不清楚我在這里做什么的人氨肌,我只是為動作類型定義常量鸿秆,然后使用ES6的lambda簡寫符號我創(chuàng)建箭頭函數(shù),返回包含類型和(可選)普通對象屬性怎囚。該類型用于標(biāo)識已分派的操作類型卿叽,有效負載是在調(diào)度操作時將數(shù)據(jù)發(fā)送到Reducer(和存儲)的方式。

現(xiàn)在我們已經(jīng)創(chuàng)建了我們的action和action creator恳守,讓我們在reducer中處理這些動作:
更新您reducers/index.js的以下內(nèi)容考婴。

import {
    FETCH_WHISKIES,
    FETCH_WHISKIES_FAILURE,
    FETCH_WHISKIES_SUCCESS
} from '../actions';

const initialState = {
    whiskies: [],
    isLoading: false,
    error: null
};

export default function rootReducer(state = initialState, action) {
    switch (action.type) {
        case FETCH_WHISKIES:
            return {
                ...state,
                // whenever we want to fetch the whiskies, set isLoading to true to show a spinner
                isLoading: true,
                error: null
            };
        case FETCH_WHISKIES_SUCCESS:
            return {
                whiskies: [...action.payload],
                // whenever the fetching finishes, we stop showing the spinner and then show the data
                isLoading: false,
                error: null
            };
        case FETCH_WHISKIES_FAILURE:
            return {
                whiskies: [],
                isLoading: false,
                // same as FETCH_WHISKIES_SUCCESS, but instead of data we will show an error message
                error: action.payload
            };
        default:
            return state;
    }
}

現(xiàn)在我們已經(jīng)完成了所有這些,我們可以最終編寫一些Redux-Observable代碼(抱歉花了這么長時間4吆妗)

轉(zhuǎn)到您的epics/index.js文件沥阱,讓我們創(chuàng)建我們的第一部史詩。首先伊群,您需要添加一些導(dǎo)入:

import { Observable } from 'rxjs';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/catch';
import { ajax } from 'rxjs/observable/dom/ajax';

import {
    FETCH_WHISKIES,
    fetchWhiskiesFailure,
    fetchWhiskiesSuccess
} from "../actions";

我們在這里做的是導(dǎo)入我們需要調(diào)度的action crator以及我們需要在動作流中觀察的action type考杉,以及來自RxJS的一些操作符以及Observable。請注意舰始,RxJS和Redux Observable都不會自動導(dǎo)入運算符奔则,因此您必須自己導(dǎo)入它們(另一種選擇是在您的條目index.js中導(dǎo)入整個'rxjs'模塊,但我不建議這樣做蔽午,因為它會給你大束尺寸)易茬。好的,讓我們來看看我們導(dǎo)入的這些運算符以及它們的作用:

map- 類似于Javascript的本機Array.map()及老,map在流中的每個項目上執(zhí)行一個函數(shù)抽莱,并返回帶有映射項目的新流/ Observable。
of - 從非Observable值創(chuàng)建一個Observable / stream(它可以是一個原語骄恶,一個對象食铐,一個函數(shù),任何東西)僧鲁。
ajax - 是提供的用于執(zhí)行AJAX請求的RxJS模塊; 我們將使用它來調(diào)用API虐呻。
catch - 用于捕獲可能發(fā)生的任何錯誤
switchMap - 這是最復(fù)雜的。它的作用是寞秃,它接受一個返回Observable的函數(shù)斟叼,每次這個內(nèi)部Observable發(fā)出一個值時,它會將該值合并到外部Observable(調(diào)用switchMap的那個)春寿。這是捕獲朗涩,每次創(chuàng)建一個新的內(nèi)部Observable時,外部Observable都會訂閱它(即偵聽值并將它們合并到自身)绑改,并取消對先前發(fā)出的Observable的所有其他訂閱谢床。這對于我們不關(guān)心先前結(jié)果是否已成功或已被取消的情況非常有用兄一。例如,當(dāng)我們發(fā)送多個用于獲取威士忌的操作時识腿,我們只需要最新的結(jié)果出革,switchMap就是這樣做的,它將訂閱最新的結(jié)果并將其合并到外部Observable并丟棄之前的請求(如果它們?nèi)晕赐瓿桑┒伤稀T趧?chuàng)建POST請求時蹋盆,您通常關(guān)心先前的請求是否已完成,以及是否使用了mergeMap硝全。mergeMap 做同樣的事情栖雾,除非它沒有取消先前的Observables。

考慮到這一點伟众,讓我們看看用于獲取威士忌的Epic將如何顯示:

const url = 'https://evening-citadel-85778.herokuapp.com/whiskey/'; // The API for the whiskies
/*
    The API returns the data in the following format:
    {
        "count": number,
        "next": "url to next page",
        "previous": "url to previous page",
        "results: array of whiskies
    }
    since we are only interested in the results array we will have to use map on our observable
 */

function fetchWhiskiesEpic(action$) { // action$ is a stream of actions
    // action$.ofType is the outer Observable
    return action$
        .ofType(FETCH_WHISKIES) // ofType(FETCH_WHISKIES) is just a simpler version of .filter(x => x.type === FETCH_WHISKIES)
        .switchMap(() => {
            // ajax calls from Observable return observables. This is how we generate the inner Observable
            return ajax
                .getJSON(url) // getJSON simply sends a GET request with Content-Type application/json
                .map(data => data.results) // get the data and extract only the results
                .map(whiskies => whiskies.map(whisky => ({
                    id: whisky.id,
                    title: "whisky.title,"
                    imageUrl: whisky.img_url
                })))// we need to iterate over the whiskies and get only the properties we need
                // filter out whiskies without image URLs (for convenience only)
                .map(whiskies => whiskies.filter(whisky => !!whisky.imageUrl))
            // at the end our inner Observable has a stream of an array of whisky objects which will be merged into the outer Observable
        })
        .map(whiskies => fetchWhiskiesSuccess(whiskies)) // map the resulting array to an action of type FETCH_WHISKIES_SUCCESS
        // every action that is contained in the stream returned from the epic is dispatched to Redux, this is why we map the actions to streams.
        // if an error occurs, create an Observable of the action to be dispatched on error. Unlike other operators, catch does not explicitly return an Observable.
        .catch(error => Observable.of(fetchWhiskiesFailure(error.message)))
}

在此之后析藕,還剩下一件事,那就是將我們的史詩添加到combineEpics函數(shù)調(diào)用中凳厢,如下所示:

export const rootEpic = combineEpics(fetchWhiskiesEpic);

好的账胧,這里有很多事,我會給你的先紫。但讓我們一塊一塊地分開治泥。

ajax.getJSON(url)返回一個Observable,其中包含來自請求的數(shù)據(jù)作為流中的值遮精。
.map(data => data.results)從Observable獲取所有值(在這種情況下只有1)居夹,results從響應(yīng)中獲取屬性并返回帶有新值的新Observable(即只有results數(shù)組)。

.map(whiskies => whiskies.map(whisky => ({
                    id: whisky.id,
                    title: "whisky.title,"
                    imageUrl: whisky.img_url
                })))

從前一個observable(結(jié)果數(shù)組)獲取值本冲,調(diào)用Array.map()它准脂,并映射數(shù)組的每個元素(每個威士忌)以創(chuàng)建一個新的對象數(shù)組,只保存每個威士忌的id檬洞,title和imageUrl狸膏,因為我們不需要別的。

.map(whiskies => whiskies.filter(whisky => !!whisky.imageUrl)) 獲取Observable中的數(shù)組并返回帶有已過濾數(shù)組的新Observable添怔。

switchMap一個包裝此代碼借此觀察到的湾戳,內(nèi)可觀測的流合并到調(diào)用可觀察到的數(shù)據(jù)流switchMap。如果另一個威士忌提取請求通過广料,則此操作將再次重復(fù)砾脑,之前的結(jié)果將被丟棄,這要歸功于switchMap性昭。

.map(whiskies => fetchWhiskiesSuccess(whiskies)) 只需獲取我們添加到流中的這個新值拦止,并將其映射到FETCH_WHISKIES_SUCCESS類型的操作县遣,該操作將在從Epic返回Observable后調(diào)度糜颠。

.catch(error => Observable.of(fetchWhiskiesFailure(error.message)))捕獲可能發(fā)生的任何錯誤汹族,只返回一個Observable。然后其兴,此Observable通過switchMap傳播顶瞒,再次將其合并到外部Observable,我們在流中獲得類型為FETCH_WHISKIES_FAILURE的操作元旬。

花點時間榴徐,這是一個復(fù)雜的過程,如果你還沒有碰過Reactive編程和RxJS匀归,它看起來和聲音都非晨幼剩可怕(閱讀我上面提供的那些鏈接!)穆端。

在此之后袱贮,我們需要做的就是渲染一個UI,它將有一個用于調(diào)度操作的按鈕和一個用于顯示數(shù)據(jù)的表体啰。我們這樣做; 首先創(chuàng)建一個名為components的新文件夾和一個名為Whisky.jsx的新組件攒巍。

import React from 'react';

const Whisky = ({ whisky }) => (
    <div>
        <img style={{ width: '300px', height: '300px' }} src={whisky.imageUrl} />
        <h3>{whisky.title}</h3>
    </div>
);

export default Whisky;

該組件只需呈現(xiàn)單個威士忌項目,其圖像和標(biāo)題荒勇。(請為了上帝的愛柒莉,永遠不要使用內(nèi)聯(lián)樣式。我在這里做它們因為它是一個簡單的例子)沽翔。

現(xiàn)在我們要渲染一個威士忌元素網(wǎng)格兢孝。讓我們創(chuàng)建一個名為WhiskyGrid.jsx的新組件。

import React from 'react';

import Whisky from './Whisky';

const WhiskyGrid = ({ whiskies }) => (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr' }}>
        {whiskies.map(whisky => (<Whisky key={whisky.id} whisky={whisky} />))}
    </div>
);

export default WhiskyGrid;

WhiskyGrid所做的是利用CSS-Grid并創(chuàng)建每行3個元素的網(wǎng)格仅偎,只需將威士忌數(shù)組作為道具傳入西潘,并將每個威士忌映射到威士忌組件。

現(xiàn)在讓我們來看看我們的App.js:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import './App.css';

import { fetchWhiskies } from './actions';

import WhiskyGrid from './components/WhiskyGrid';

class App extends Component {
  render() {
    const {
      fetchWhiskies,
      isLoading,
      error,
      whiskies
    } = this.props;

    return (
      <div class>
        <button onClick={fetchWhiskies}>Fetch whiskies</button>
        {isLoading && <h1>Fetching data</h1>}
        {!isLoading && !error && <WhiskyGrid whiskies={whiskies} />}
        {error && <h1>{error}</h1>}
      </div>
    );
  }
}

const mapStateToProps = state => ({ ...state });

const mapDispatchToProps = dispatch =>
    bindActionCreators({
        fetchWhiskies
    }, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(App);

如您所見哨颂,這里有很多修改喷市。首先,我們必須將Redux存儲和動作創(chuàng)建器綁定到組件的props威恼。我們使用connectreact-redux中的HOC來實現(xiàn)這一目標(biāo)品姓。之后,我們創(chuàng)建一個div箫措,它有一個按鈕腹备,其onClick設(shè)置為調(diào)用fetchWhiskies動作創(chuàng)建者,現(xiàn)在綁定到dispatch斤蔓。單擊該按鈕將調(diào)度FETCH_WHISKIES操作植酥,我們的Redux Observable epic將拾取它,從而調(diào)用API。接下來我們有一個條件友驮,如果Redux存儲中的isLoading屬性為true(已調(diào)度FETCH_WHISKIES但既未完成也未拋出錯誤)漂羊,我們將顯示一條文本,說明加載數(shù)據(jù)卸留。如果數(shù)據(jù)未加載且沒有錯誤走越,我們將渲染WhiskyGrid組件并將Redux中的威士忌作為道具傳遞。如果error不為null耻瑟,則呈現(xiàn)錯誤消息旨指。

結(jié)論

響應(yīng)式并不容易。它提出了一種完全不同的編程范式喳整,它迫使你以不同的方式思考谆构。我不會說功能性比面向?qū)ο蟾茫蛘哒fReactive是最好的框都。最好的編程范式低淡,IN MY OPINION,是范式的組合瞬项。但是蔗蹋,我相信Redux Observable確實提供了其他異步Redux中間件的絕佳替代方案,在您通過學(xué)習(xí)曲線之后囱淋,您將獲得一種處理異步事件的神奇猪杭,自然的方法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末妥衣,一起剝皮案震驚了整個濱河市皂吮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌税手,老刑警劉巖蜂筹,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異芦倒,居然都是意外死亡艺挪,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門兵扬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來麻裳,“玉大人,你說我怎么就攤上這事器钟〗蚩樱” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵傲霸,是天一觀的道長疆瑰。 經(jīng)常有香客問我眉反,道長,這世上最難降的妖魔是什么穆役? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任寸五,我火速辦了婚禮,結(jié)果婚禮上孵睬,老公的妹妹穿的比我還像新娘播歼。我一直安慰自己伶跷,他們只是感情好掰读,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著叭莫,像睡著了一般蹈集。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雇初,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天拢肆,我揣著相機與錄音,去河邊找鬼靖诗。 笑死郭怪,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的刊橘。 我是一名探鬼主播鄙才,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼促绵!你這毒婦竟也來了攒庵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤败晴,失蹤者是張志新(化名)和其女友劉穎浓冒,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尖坤,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡稳懒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了慢味。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片僚祷。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贮缕,靈堂內(nèi)的尸體忽然破棺而出辙谜,到底是詐尸還是另有隱情,我是刑警寧澤感昼,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布装哆,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蜕琴。R本人自食惡果不足惜萍桌,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凌简。 院中可真熱鬧上炎,春花似錦、人聲如沸雏搂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凸郑。三九已至裳食,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芙沥,已是汗流浹背诲祸。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留而昨,地道東北人救氯。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像歌憨,于是被迫代替她去往敵國和親着憨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容