Redux-saga

Redux-saga

概述

redux 一文中我們有說過處理異步我們應(yīng)該放在 reducer 之前,所以我們需要中間件來處理我們的異步操作拂酣。redux-saga 就是一個(gè)用于管理 redux 應(yīng)用的異步操作中間件,redux-saga 通過創(chuàng)建 sagas 將所有異步操作的邏輯都收集在一個(gè)位置集中處理痛侍。

簡(jiǎn)單的來說就是兩步:

  • sagas 負(fù)責(zé)協(xié)調(diào)那些復(fù)雜或者異步的操作
  • reducer 負(fù)責(zé)處理 actionstage 更新

數(shù)據(jù)流

我們先看一張圖來大致了解下整個(gè)過程:

6493119-f4c44eb340874014.png

有了之前 redux 的基礎(chǔ)惜颇,這張圖其實(shí)還是比較容易理解的,下面我們來簡(jiǎn)單說下:

  1. React 組件由用戶觸發(fā)事件壶运,通過 action creator 發(fā)起 action
  2. sagas 監(jiān)聽發(fā)起的 action,然后決定基于這個(gè) action 做什么
  3. 緊接著根據(jù)上步所做處理將 effects(sage的任務(wù)單元浪秘,簡(jiǎn)單的 JavaScript 對(duì)象)傳給 reducer
  4. 最后由 reducer 返回新的 state

安裝

yarn add redux-saga 
// npm install redux-saga -S

使用實(shí)例

配置

import { createStore, combineReducers, applyMiddleware } from 'redux';
import global from './reducers/global';
import login from './reducers/login';
import createSagaMiddleware from 'redux-saga';       // 引入redux-saga中的createSagaMiddleware函數(shù)
import rootSaga from '../sagas';                    // 引入saga.js

const sagaMiddleware = createSagaMiddleware()        // 執(zhí)行

const reducerAll = {
  global,
  login
}


export const store = createStore(
    combineReducers({...reducerAll}),               // 合并reducer
    applyMiddleware(sagaMiddleware)                 // 中間件蒋情,加載sagaMiddleware
)

sagaMiddleware.run(rootSaga)

ui組件觸發(fā)action創(chuàng)建函數(shù)

handleSubmit = (e) => {
    this.props.form.validateFields((err, values) => {
      if (!err) {
        console.log('Received values of form: ', values);
        this.props.onSubmit(values);
      }
    });
  }

<Form onSubmit={this.handleSubmit} className="login-form">
    ...
</Form>

將action傳入saga

const mapDispatchToProps = dispatch => {
  return {
    onSubmit: (payload) => {
      dispatch({ type: LOGIN_REQUEST, ...payload })
    }
  }
}

saga捕獲action創(chuàng)建函數(shù)返回的action(effects)

function* fetchLogin(userName, password) {
  try {
    console.log("saga:");
    const token = yield call(axios.get({ url: '' }))
    yield put({ type: 'LOGIN_SUCCESS', token })
  } catch(error) {
    yield put({ type: 'LOGIN_ERROR', error })
  }
}

export default function* watchIsLogin(dispatch) {
  while(true) {
    const { userName, password } = yield take('LOGIN_REQUEST');
    yield fork(fetchLogin, userName, password)
  }
}

reducer接受并返回新的state

const initState = {
  userName: 'Cola'
}

export default function(state = initState, action){
  switch (action.type) {
    case LOGIN_IN:
      return {...state, ...action.payload};
    default:
      return state;
  }
}

看了上面的步驟是不是覺得整體邏輯還是非常清晰明了的,下面我們把上面代碼中未講解到的代碼名詞解釋下耸携。

名詞解釋

Effect

一個(gè) effect 就是一個(gè)純文本 javascript 對(duì)象棵癣,包含一些將被 saga middleware 執(zhí)行的指令。那么如何創(chuàng)建 effect 呢夺衍?使用下面我們馬上要講到API中 redux-saga 提供的工廠函數(shù)來創(chuàng)建 effect

Task

一個(gè) task 就像是一個(gè)在后臺(tái)運(yùn)行的進(jìn)程狈谊。在基于 redux-saga 的應(yīng)用程序中,可以同時(shí)運(yùn)行多個(gè) task沟沙。通過 fork 函數(shù)來創(chuàng)建 task

阻塞調(diào)用/非阻塞調(diào)用

阻塞調(diào)用的意思是河劝,Sagayield Effect 之后會(huì)等待其執(zhí)行結(jié)果返回,結(jié)果返回后才會(huì)恢復(fù)執(zhí)行 Generator 中的下一個(gè)指令矛紫。

非阻塞調(diào)用的意思是赎瞎,Saga 會(huì)在 yield Effect 之后立即恢復(fù)執(zhí)行。

function* saga() {
  yield take(ACTION)              // 阻塞: 將等待 action
  yield call(ApiFn, ...args)      // 阻塞: 將等待 ApiFn (如果 ApiFn 返回一個(gè) Promise 的話)
  yield call(otherSaga, ...args)  // 阻塞: 將等待 otherSaga 結(jié)束

  yield put(ACTION)                   // 非阻塞
  yield put(channel, ACTION)                   // 當(dāng) put 沒有被緩存而是被 taker 立即消費(fèi)掉時(shí)颊咬,阻塞

  const task = yield fork(otherSaga, ...args)  // 非阻塞: 將不會(huì)等待 otherSaga
  yield cancel(task)                           // 非阻塞: 將立即恢復(fù)執(zhí)行
  // or
  yield join(task)                             // 阻塞: 將等待 task 結(jié)束
}

Watcher/Worker

指的是一種使用兩個(gè)單獨(dú)的 Saga 來組織控制流的方式务甥。

  • Watcher: 監(jiān)聽發(fā)起的 action 并在每次接收到 action 時(shí) fork 一個(gè) worker
  • Worker: 處理 action 并結(jié)束它喳篇。
function* watcher() {
  while(true) {
    const action = yield take(ACTION)
    yield fork(worker, action.payload)
  }
}

function* worker(payload) {
  // ... do some stuff
}

Middleware API

createSagaMiddleware(options)

創(chuàng)建一個(gè) Redux middleware敞临,并將 Sagas 連接到 Redux Store

options : Object - 傳遞給 middleware 的選項(xiàng)列表麸澜。

middleware.run(saga, ...args)

動(dòng)態(tài)執(zhí)行 saga哟绊。用于 applyMiddleware 階段之后執(zhí)行 Sagas。這個(gè)方法返回一個(gè)
Task 描述對(duì)象痰憎。

Saga輔助函數(shù)

takeEvery

在發(fā)起(dispatch)到 Store 并且匹配 pattern 的每一個(gè) action 上派生一個(gè) saga。簡(jiǎn)單來說就是監(jiān)聽所有的匹配到的 action攀涵。

import { takeEvery } from `redux-saga/effects`

function* fetchUser(action) {
  ...
}

function* watchFetchUser() {
  yield takeEvery('USER_REQUESTED', fetchUser)
}

takeLatest

在發(fā)起到 Store 并且匹配 pattern 的每一個(gè) action 上派生一個(gè) saga铣耘。并自動(dòng)取消之前所有已經(jīng)啟動(dòng)但仍在執(zhí)行中的 saga 任務(wù)。

import { takeLatest } from `redux-saga/effects`

function* fetchUser(action) {
  ...
}

function* watchLastFetchUser() {
  yield takeLatest('USER_REQUESTED', fetchUser)
}

這樣便可以保證:即使用戶以極快的速度連續(xù)多次觸發(fā) USER_REQUESTED action以故,我們都只會(huì)以最后的一個(gè)結(jié)束蜗细。

takeLeading

在發(fā)起到 Store 并且匹配 pattern 的每一個(gè) action 上派生一個(gè) saga。 它將在派生一次任務(wù)之后阻塞,直到派生的 saga 完成炉媒,然后又再次開始監(jiān)聽指定的 pattern踪区。

import { takeLeading } from `redux-saga/effects`

function* fetchUser(action) {
  ...
}

function* watchLastFetchUser() {
  yield takeLeading('USER_REQUESTED', fetchUser)
}

由于 takeLeading 在其開始之后便無視所有新傳入的任務(wù),我們便可以保證:如果用戶以極快的速度連續(xù)多次觸發(fā) USER_REQUESTED action吊骤,我們都只會(huì)保持以第一個(gè) action 運(yùn)行缎岗。

throttle

它在派生一次任務(wù)之后,仍然將新傳入的 action 接收到底層的 buffer 中白粉,至多保留(最近的)一個(gè)传泊。但與此同時(shí),它在 ms 毫秒內(nèi)將暫停派生新的任務(wù) —— 這也就是它被命名為節(jié)流閥(throttle)的原因鸭巴。其用途眷细,是在處理任務(wù)時(shí),無視給定的時(shí)長(zhǎng)內(nèi)新傳入的 action鹃祖。

import { call, put, throttle } from `redux-saga/effects`

function* fetchAutocomplete(action) {
  const autocompleteProposals = yield call(Api.fetchAutocomplete, action.text)
  yield put({type: 'FETCHED_AUTOCOMPLETE_PROPOSALS', proposals: autocompleteProposals})
}

function* throttleAutocomplete() {
  yield throttle(1000, 'FETCH_AUTOCOMPLETE', fetchAutocomplete)
}

上面的代碼我們通過 throttle 無視了一段時(shí)間內(nèi)連續(xù)的 FETCH_AUTOCOMPLETE溪椎,我們便可以確保用戶不會(huì)因此向我們的服務(wù)器發(fā)起大量請(qǐng)求。

Effect 創(chuàng)建器

注意:

  • 以下每個(gè)函數(shù)都會(huì)返回一個(gè)普通 Javascript 對(duì)象(plain JavaScript > > object)恬口,并且不會(huì)執(zhí)行任何其它操作校读。
  • 執(zhí)行是由 middleware 在上述迭代過程中進(jìn)行的。
  • middleware 會(huì)檢查每個(gè) Effect 的描述信息楷兽,并進(jìn)行相應(yīng)的操作

take

創(chuàng)建一個(gè) Effect 描述信息地熄,用來命令 middlewareStore 上等待指定的 action。 在發(fā)起匹配的 action 之前芯杀,Generator 將暫停端考。

put

創(chuàng)建一個(gè) Effect 描述信息,用來命令 middlewareStore 發(fā)起一個(gè) action揭厚。 這個(gè) effect 是非阻塞型的却特,并且所有向下游拋出的錯(cuò)誤(例如在 reducer 中),都不會(huì)冒泡回到 saga 當(dāng)中筛圆。

call(fn, ...args)

創(chuàng)建一個(gè) Effect 描述信息裂明,用來命令 middleware 以參數(shù) args 調(diào)用函數(shù) fn

fork(fn, ...args)

創(chuàng)建一個(gè) Effect 描述信息太援,用來命令 middleware非阻塞調(diào)用 的形式執(zhí)行 fn闽晦。

join(task)

創(chuàng)建一個(gè) Effect 描述信息,用來命令 middleware 等待之前的一個(gè)分叉任務(wù)的結(jié)果提岔。

cancel(task)

創(chuàng)建一個(gè) Effect 描述信息仙蛉,用來命令 middleware 取消之前的一個(gè)分叉任務(wù)。

select(selector, ...args)

創(chuàng)建一個(gè) Effect碱蒙,用來命令 middleware 在當(dāng)前 Storestate 上調(diào)用指定的選擇器(即返回 selector(getState(), ...args) 的結(jié)果)

例如荠瘪,假設(shè)我們?cè)趹?yīng)用程序中有這樣結(jié)構(gòu)的一份 state

state = {
  cart: {...}
}

我們創(chuàng)建一個(gè) 選擇器(selector)夯巷,即一個(gè)知道如果從 State 中提取 cart 數(shù)據(jù)的函數(shù):

// selectors.js
export const getCart = state => state.cart

然后,我們可以使用 select EffectSaga 的內(nèi)部使用該選擇器:

import { take, fork, select } from 'redux-saga/effects'
import { getCart } from './selectors'

function* checkout() {
  // 使用被導(dǎo)出的選擇器查詢 state
  const cart = yield select(getCart)

  // ... 調(diào)用某些 API哀墓,然后發(fā)起一個(gè) success/error action
}

export default function* rootSaga() {
  while (true) {
    yield take('CHECKOUT_REQUEST')
    yield fork(checkout)
  }
}

actionChannel(pattern, [buffer])

創(chuàng)建一個(gè) Effect趁餐,用來命令 middleware 通過一個(gè)事件 channel 對(duì)匹配 patternaction 進(jìn)行排序。 作為可選項(xiàng)篮绰,你也可以提供一個(gè) buffer 來控制如何緩存排序的 actions后雷。

import { actionChannel, call } from 'redux-saga/effects'
import api from '...'

function* takeOneAtMost() {
  const chan = yield actionChannel('USER_REQUEST')
  while (true) {
    const {payload} = yield take(chan)
    yield call(api.getUser, payload)
  }
}

flush(channel)

創(chuàng)建一個(gè) Effect,用來命令 middlewarechannel 中沖除所有被緩存的數(shù)據(jù)阶牍。被沖除的數(shù)據(jù)會(huì)返回至 saga喷面,這樣便可以在需要的時(shí)候再次被利用。

function* saga() {
  const chan = yield actionChannel('ACTION')

  try {
    while (true) {
      const action = yield take(chan)
      // ...
    }
  } finally {
    const actions = yield flush(chan)
    // ...
  }

}

cancelled()

創(chuàng)建一個(gè) Effect走孽,用來命令 middleware 返回該 generator 是否已經(jīng)被取消惧辈。通常你會(huì)在 finally 區(qū)塊中使用這個(gè) Effect 來運(yùn)行取消時(shí)專用的代碼。

function* saga() {
  try {
    // ...
  } finally {
    if (yield cancelled()) {
      // 只應(yīng)在取消時(shí)執(zhí)行的邏輯
    }
    // 應(yīng)在所有情況下都執(zhí)行的邏輯(例如關(guān)閉一個(gè) channel)
  }
}

setContext(props)

創(chuàng)建一個(gè) effect磕瓷,用來命令 middleware 更新其自身的上下文盒齿。這個(gè) effect 擴(kuò)展了 saga 的上下文,而不是代替困食。

getContext(prop)

創(chuàng)建一個(gè) effect边翁,用來命令 middleware 返回 saga 的上下文中的一個(gè)特定屬性。

Effect 組合器

race(effects)

創(chuàng)建一個(gè) Effect 描述信息硕盹,用來命令 middleware 在多個(gè) Effect 間運(yùn)行 競(jìng)賽(Race)(與 Promise.race([...]) 的行為類似)符匾。

import { take, call, race } from `redux-saga/effects`
import fetchUsers from './path/to/fetchUsers'

function* fetchUsersSaga {
  const { response, cancel } = yield race({
    response: call(fetchUsers),
    cancel: take(CANCEL_FETCH)
  })
}

all(effects)

創(chuàng)建一個(gè) Effect 描述信息,用來命令 middleware 并行地運(yùn)行多個(gè) Effect瘩例,并等待它們?nèi)客瓿砂〗骸_@是與標(biāo)準(zhǔn)的 Promise#all 相當(dāng)對(duì)應(yīng)的 API

import { fetchCustomers, fetchProducts } from './path/to/api'
import { all, call } from `redux-saga/effects`

function* mySaga() {
  const { customers, products } = yield all({
    customers: call(fetchCustomers),
    products: call(fetchProducts)
  })
}

接口

Task

方法 返回值
task.isRunning() 若任務(wù)還未返回或拋出了一個(gè)錯(cuò)誤則為 true
task.isCancelled() 若任務(wù)已被取消則為 true
task.result() 任務(wù)的返回值垛贤。若任務(wù)仍在運(yùn)行中則為 undefined
task.error() 任務(wù)拋出的錯(cuò)誤焰坪。若任務(wù)仍在執(zhí)行中則為 undefined
task.done 一個(gè) Promise
task.cancel() 取消任務(wù)(如果任務(wù)仍在執(zhí)行中)

Channel

方法 返回值
Channel.take(callback) 用于注冊(cè)一個(gè) taker
Channel.put(message) 用于在 buffer 上放入消息
Channel.flush(callback) 用于從 channel 中提取所有被緩存的消息
Channel.close() 關(guān)閉 channel,意味著不再允許做放入操作

Buffer

方法 返回值
Buffer.isEmpty() 如果緩存中沒有消息則返回
Buffer.put(message) 用于往緩存中放入新的消息
Buffer.take() 用于檢索任何被緩存的消息

SagaMonitor

用于由 middleware 發(fā)起監(jiān)視(monitor)事件聘惦。實(shí)際上某饰,middleware 發(fā)起 5 個(gè)事件:

  • 當(dāng)一個(gè) effect 被觸發(fā)時(shí)(通過 yield someEffect),middleware 調(diào)用 sagaMonitor.effectTriggered(options) : options 包括:

    • effectId : Number - 分配給 yielded effect 的唯一 ID

    • parentEffectId : Number - 父級(jí) EffectID善绎。在 raceparallel effect 的情況下黔漂,所有在內(nèi)部 yieldeffect 都將有一個(gè)直接 race/parallel 的父級(jí) effect。在最頂級(jí)的 effect 的情況下禀酱,父級(jí)是包裹它的 Saga炬守。

    • label : String - 在 race effect 的情況下,所有子 effect 都將被指定為傳遞給 race 的對(duì)象中對(duì)應(yīng)鍵的標(biāo)簽比勉。

    • effect : Object - yielded effect 其自身

  • 如果該 effect 成功地被 resolve,則 middleware 調(diào)用 sagaMonitor.effectResolved(effectId, result)

    • effectId : Number - yielded effectID

    • result : any - 該 effect 成功 resolve 的結(jié)果。在 forkspawn 的情況下浩聋,結(jié)果將是一個(gè) Task 對(duì)象观蜗。

  • 如果該 effect 因一個(gè)錯(cuò)誤被 reject,則 middleware 調(diào)用 sagaMonitor.effectRejected

    • effectId : Number - yielded effectID

    • error : any - 該 effect reject 的錯(cuò)誤

  • 如果該 effect 被取消衣洁,則 middleware 調(diào)用 sagaMonitor.effectCancelled

    • effectId : Number - yielded effectID
  • 最后墓捻,當(dāng) Redux action 被發(fā)起時(shí),middleware 調(diào)用 sagaMonitor.actionDispatched

    • action : Object - 被發(fā)起的 Redux action坊夫。如果該 action 是由一個(gè) Saga 發(fā)起的砖第,那么該 action 將擁有一個(gè)屬性 SAGA_ACTION 并被設(shè)為 true(你可以從 redux-saga/utils 中導(dǎo)入 SAGA_ACTION)。

外部API

不做贅述环凿,可根據(jù)自己需要使用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末梧兼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子智听,更是在濱河造成了極大的恐慌羽杰,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件到推,死亡現(xiàn)場(chǎng)離奇詭異考赛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)莉测,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門颜骤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捣卤,你說我怎么就攤上這事忍抽。” “怎么了腌零?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵梯找,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我益涧,道長(zhǎng)锈锤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任闲询,我火速辦了婚禮久免,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扭弧。我一直安慰自己阎姥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布鸽捻。 她就那樣靜靜地躺著呼巴,像睡著了一般泽腮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上衣赶,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天诊赊,我揣著相機(jī)與錄音,去河邊找鬼府瞄。 笑死碧磅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的遵馆。 我是一名探鬼主播鲸郊,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼货邓!你這毒婦竟也來了秆撮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤逻恐,失蹤者是張志新(化名)和其女友劉穎像吻,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體复隆,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拨匆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挽拂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惭每。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亏栈,靈堂內(nèi)的尸體忽然破棺而出台腥,到底是詐尸還是另有隱情,我是刑警寧澤绒北,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布黎侈,位于F島的核電站,受9級(jí)特大地震影響闷游,放射性物質(zhì)發(fā)生泄漏峻汉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一脐往、第九天 我趴在偏房一處隱蔽的房頂上張望休吠。 院中可真熱鬧,春花似錦业簿、人聲如沸瘤礁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柜思。三九已至岩调,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赡盘,已是汗流浹背誊辉。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亡脑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓邀跃,卻偏偏與公主長(zhǎng)得像霉咨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拍屑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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

  • Redux-saga 概述 redux-saga是一個(gè)用于管理redux應(yīng)用異步操作的中間件途戒,redux-saga...
    woow_wu7閱讀 51,701評(píng)論 11 41
  • redux-saga框架使用詳解及Demo教程 前面我們講解過redux框架和dva框架的基本使用,因?yàn)閐va框架...
    光強(qiáng)_上海閱讀 22,047評(píng)論 8 46
  • 前端時(shí)間一直在補(bǔ)大學(xué)的知識(shí)僵驰,很慚愧==喷斋,今天帶來一個(gè)項(xiàng)目中用的知識(shí),目前的技術(shù)棧是ant design + dva...
    tobAlier閱讀 2,413評(píng)論 0 3
  • 1. redux-thunk處理副作用的缺點(diǎn) 1.1 redux的副作用處理 redux中的數(shù)據(jù)流大致是: UI—...
    Grace_ji閱讀 3,542評(píng)論 0 14
  • 開封位于中原腹地的豫東平原蒜茴,具有悠久歷史星爪,曾為七朝古都。北宋時(shí)名東京粉私、汴京顽腾,達(dá)到鼎盛輝煌,極其繁榮诺核,是當(dāng)時(shí)世界特大...
    胡老太閱讀 1,918評(píng)論 31 52