項目中用到的React待逞、React-Redux嗤无、Redux-saga簡介

  1. React有props和state:
    props意味著父級分發(fā)下來的屬性
    state意味著組件內(nèi)部可以自行管理的狀態(tài),并且整個React沒有數(shù)據(jù)向上回溯的能力,這就是react的單向數(shù)據(jù)流侵佃。這就意味著如果是一個數(shù)據(jù)狀態(tài)非常復雜的應用涌献,更多的時候發(fā)現(xiàn)React根本無法讓兩個組件互相交流,使用對方的數(shù)據(jù)您旁,react的通過層級傳遞數(shù)據(jù)的這種方法是非常難受的侦锯,這個時候,迫切需要一個機制,把所有的state集中到組件頂部题篷,能夠靈活的將所有state各取所需的分發(fā)給所有的組件,這就是redux
  2. Redux的誕生是為了給 React 應用提供「可預測化的狀態(tài)管理」機制。
  • Redux會將整個應用狀態(tài)(其實也就是數(shù)據(jù))存儲到到一個地方,稱為store(就是一個數(shù)據(jù)池苫幢,一個應用只有一個)九榔,這個store里面保存一棵狀態(tài)樹(state tree),組件改變state的唯一方法是通過調(diào)用store的dispatch方法,觸發(fā)一個action先朦,這個action被對應的reducer(reducer就是改變state的處理層,它接收action和state迂苛,通過處理action來返回新的state)處理念搬,于是state完成更新
    組件可以派發(fā)(dispatch)行為(action)給store,而不是直接通知其它組件,其它組件可以通過訂閱store中的狀態(tài)(state)來刷新自己的視圖
  • 使用步驟
    1.創(chuàng)建reducer
    可以使用單獨的一個reducer,也可以將多個reducer合并為一個reducer,即:combineReducers()
    action發(fā)出命令后將state放入reducer加工函數(shù)中袖外,返回新的state,對state進行加工處理
    2.創(chuàng)建action
    用戶是接觸不到state的魄幕,只能由view觸發(fā)留储,action可以理解為指令,需要發(fā)出多少動作就有多少指令,action是一個對象,其中的type屬性是必須的,定義action類型,其他屬性可以自由設置
    3.創(chuàng)建store,使用createStore方法。store 可以理解為有多個加工機器的總工廠,提供subscribe产还,dispatch炕柔,getState這些方法欢嘿。


  1. 如果把store直接集成到React應用的頂層props里面捕捂,只要各個子組件能訪問到頂層props就可以了,比如這樣:
<頂層組件 store={store}>
 <App />
</頂層組件>

這就是 react-redux,是為了讓redux更好的適用于react而生的一個庫狞山。react-redux將組件區(qū)分為容器組件 和 UI 組件勘纯,前者會處理邏輯,后者只負責顯示和交互,內(nèi)部不處理邏輯穿挨,狀態(tài)完全由外部掌控。

  • 兩個核心
    Provider
    一般我們都將頂層組件包裹在Provider組件之中,這樣,所有組件就都可以在react-redux的控制之下了秫逝,但是store必須作為參數(shù)放到Provider組件中狈醉。
 <Provider store = {store}>
 <App />
<Provider>
// 這樣,所有組件都能夠訪問到Redux中的數(shù)據(jù)

connect
這個方法可以從UI組件生成容器組件抑进,但容器組件的定位是處理數(shù)據(jù)炬称、響應行為棘利,因此,需要對UI組件添加額外的東西劳坑,即mapStateToProps和mapDispatchToProps,也就是在組件外加了一層state,用法如下:

connect(mapStateToProps, mapDispatchToProps)(MyComponent)

mapStateToProps
把Redux中的數(shù)據(jù)映射到React中的props中去诚欠,舉例:

const mapStateToProps = (state) => {
  return {
    foo: state.bar
  }
}

然后渲染的時候就可以使用this.props.foo

class Foo extends Component {
 constructor(props){
  super(props);
 }
 render(){
  return(
   <div>this.props.foo</div>
  )
 }
}
Foo = connect()(Foo);
export default Foo;

然后這樣就可以完成渲染了
mapDispatchToProps
把各種dispatch也變成了可以直接使用的props,舉例:

const mapDispatchToProps = (dispatch) => { 
 return {
   onClick: () => {
    dispatch({type: 'increatment'});
   }
 }
}
class Foo extends Component {
 constructor(props){
  super(props);
 }
 render(){
  return(
    <button onClick = {this.props.onClick}>click increase</button>
  )
 }
}
Foo = connect()(Foo);
export default Foo;

組件可以直接通過this.props.onClick來調(diào)用dispatch,就不需要在代碼中來進行store.dispatch了

  1. 如果按照原始的redux工作流程撮抓,當組件中產(chǎn)生一個action后會直接觸發(fā)reducer修改state,reducer又是一個純函數(shù)生宛,也就是不能在reducer中進行異步操作;而往往實際中酥馍,組件中發(fā)生的action在進入reducer之前需要完成一個異步任務(比如發(fā)送ajax請求),拿到數(shù)據(jù)后再進入reducer瑞躺,這個時候就需要一個中間件來處理這種業(yè)務場景岸售,目前最優(yōu)雅的處理方式是redux-saga中間件,它通過 Generator 函數(shù)來創(chuàng)建妖枚,可以用同步的方式寫異步的代碼,目的是更好臼隔、更易地解決異步操作(把所有異步請求集中處理)狡逢。
    redux-saga提供了一些輔助函數(shù),用來在一些特定的action 被發(fā)起到Store時派生任務莺丑,我們先來看兩個輔助函數(shù):takeEvery 和 takeLatest
    takeEvery
    takeEvery轻局,同一個action多次觸發(fā),每個都會執(zhí)行
    例如:每次點擊按鈕去Fetch數(shù)據(jù)時姨蝴,我們發(fā)起一個 FETCH_REQUESTED 的 action,想通過啟動一個任務從服務器獲取一些數(shù)據(jù),來處理這個action
    首先創(chuàng)建一個將執(zhí)行異步 action 的任務
// put:你就認為put就等于 dispatch就可以了
// call:可以理解為實行一個異步函數(shù),是阻塞型的远豺,只有運行完后面的函數(shù)检眯,才會繼續(xù)往下
import { call, put } from 'redux-saga/effects'
export function* fetchData(action) {
 try {
  const apiAjax = (params) => fetch(url, params);
  const data = yield call(apiAjax);
  yield put({type: "FETCH_SUCCEEDED", data});
 } catch (error) {
  yield put({type: "FETCH_FAILED", error});
 }
}

然后在每次 FETCH_REQUESTED action 被發(fā)起時啟動上面的任務,也就相當于每次觸發(fā)一個名字為 FETCH_REQUESTED 的action就會執(zhí)行上邊的任務,代碼如下

import { takeEvery } from 'redux-saga'
function* watchFetchData() {
 yield* takeEvery("FETCH_REQUESTED", fetchData)
}

takeLatest
在上面的例子中锰瘸,takeEvery 允許多個 fetchData 實例同時啟動管削,在某個特定時刻含潘,我們可以啟動一個新的 fetchData 任務肮砾, 盡管之前還有一個或多個 fetchData 尚未結束,如果我們只想得到最新那個請求的響應(例如歇拆,始終顯示最新版本的數(shù)據(jù))鞋屈,我們可以使用 takeLatest

import { takeLatest } from 'redux-saga'
function* watchFetchData() {
 yield* takeLatest('FETCH_REQUESTED', fetchData)
}

和takeEvery不同,在任何時刻 takeLatest 只允許執(zhí)行一個 fetchData 任務故觅,并且這個任務是最后被啟動的那個厂庇,如果之前已經(jīng)有一個任務在執(zhí)行,那之前的這個任務會自動被取消
Effect Creators
redux-saga框架提供了很多創(chuàng)建effect的函數(shù)输吏,下面我們就來簡單的介紹下開發(fā)中最常用的幾種

  • take(pattern)
  • put(action)
  • call(fn, ...args)
  • fork(fn, ...args)
  • select(selector, ...args)

take(pattern)
take函數(shù)可以理解為監(jiān)聽未來的action权旷,它創(chuàng)建了一個命令對象,告訴middleware等待一個特定的action贯溅,Generator會暫停拄氯,直到一個與pattern匹配的action被發(fā)起,才會繼續(xù)執(zhí)行下面的語句它浅,也就是說译柏,take是一個阻塞的 effect,用法:

function* watchFetchData() {
 while(true) {
 // 監(jiān)聽一個type為 'FETCH_REQUESTED' 的action的執(zhí)行姐霍,直到等到這個Action被觸發(fā)鄙麦,才會接著執(zhí)行下面的 yield fork(fetchData) 語句
  yield take('FETCH_REQUESTED');
  yield fork(fetchData);
 }
}

put(action)
put函數(shù)是用來發(fā)送action的 effect,你可以簡單的把它理解成為redux框架中的dispatch函數(shù)镊折,當put一個action后胯府,reducer中就會計算新的state并返回,put 也是阻塞 effect腌乡,用法:

export function* toggleItemFlow() {
 let list = []
 // 發(fā)送一個type為 'UPDATE_DATA' 的Action盟劫,用來更新數(shù)據(jù),參數(shù)為 `data:list`
 yield put({
  type: actionTypes.UPDATE_DATA,
  data: list
 })
}

call(fn, ...args)
call函數(shù)簡單的理解為可以調(diào)用其他函數(shù)的函數(shù)与纽,它命令 middleware 來調(diào)用fn 函數(shù)侣签,args為函數(shù)的參數(shù)塘装,注意:fn 函數(shù)可以是一個 Generator 函數(shù),也可以是一個返回 Promise 的普通函數(shù)影所,call 函數(shù)也是阻塞 effect蹦肴,用法:

export const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
export function* removeItem() {
 try {
 // 這里call 函數(shù)就調(diào)用了 delay 函數(shù),delay 函數(shù)為一個返回promise 的函數(shù)
 return yield call(delay, 500)
 } catch (err) {
 yield put({type: actionTypes.ERROR})
 }
}

fork(fn, ...args)
fork 函數(shù)和 call 函數(shù)很像猴娩,都是用來調(diào)用其他函數(shù)的阴幌,但是fork函數(shù)是非阻塞函數(shù),也就是說卷中,程序執(zhí)行完 yield fork(fn矛双, args) 這一行代碼后,會立即接著執(zhí)行下一行代碼語句蟆豫,而不會等待fn函數(shù)返回結果后再執(zhí)行下面的語句议忽,用法:

import { fork } from 'redux-saga/effects'
export default function* rootSaga() {
 // 下面的四個 Generator 函數(shù)會一次執(zhí)行,不會阻塞執(zhí)行
 yield fork(addItemFlow)
 yield fork(removeItemFlow)
 yield fork(toggleItemFlow)
 yield fork(modifyItem)
}

select(selector, ...args)
select 函數(shù)是用來指示 middleware調(diào)用提供的選擇器獲取Store上的state數(shù)據(jù)十减,可以簡單的把它理解為redux框架中獲取store上的 state數(shù)據(jù)一樣的功能 :store.getState(),用法:

export function* toggleItemFlow() {
  // 通過 select effect 來獲取 全局 state上的 `getTodoList` 中的 list
  let tempList = yield select(state => state.getTodoList.list)
}

一個具體的實例

  • index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, applyMiddleware} from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootSaga from './sagas'
import Counter from './Counter'
import rootReducer from './reducers'

const sagaMiddleware = createSagaMiddleware() // 創(chuàng)建了一個saga中間件實例

// 下邊這句話和下邊的兩行代碼創(chuàng)建store的方式是一樣的
// const store = createStore(reducers,applyMiddlecare(middlewares))
 
const createStoreWithMiddleware = applyMiddleware(middlewares)(createStore)
const store = createStoreWithMiddleware(rootReducer)
sagaMiddleware.run(rootSaga)
 
const action = type => store.dispatch({ type })
 
function render() {
 ReactDOM.render(
 <Counter
  value={store.getState()}
  onIncrement={() => action('INCREMENT')}
  onDecrement={() => action('DECREMENT')}
  onIncrementAsync={() => action('INCREMENT_ASYNC')} />,
 document.getElementById('root')
 )
}
render()
store.subscribe(render)
  • sagas.js
import { put, call, take,fork } from 'redux-saga/effects';
import { takeEvery, takeLatest } from 'redux-saga'
 
export const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
 
function* incrementAsync() {
 // 延遲 1s 在執(zhí)行 + 1操作
 yield call(delay, 1000);
 yield put({ type: 'INCREMENT' });
}
 
export default function* rootSaga() {
 // while(true){
 // yield take('INCREMENT_ASYNC');
 // yield fork(incrementAsync);
 // }
 
 // 下面的寫法與上面的寫法上等效
 yield* takeEvery("INCREMENT_ASYNC", incrementAsync)
}
  • reducer.js
export default function counter(state = 0, action) {
 switch (action.type) {
 case 'INCREMENT':
  return state + 1
 case 'DECREMENT':
  return state - 1
 case 'INCREMENT_ASYNC':
  return state
 default:
  return state
 }
}

redux-saga基本用法總結:

  • 使用 createSagaMiddleware 方法創(chuàng)建 saga 的 Middleware 腿短,然后在創(chuàng)建的 redux 的 store 時句占,使用 applyMiddleware 函數(shù)將創(chuàng)建的 saga Middleware 實例綁定到 store 上,最后可以調(diào)用 saga Middleware 的 run 函數(shù)來執(zhí)行某個或者某些 Middleware 。
  • 在 saga 的 Middleware 中潘拱,可以使用 takeEvery 或者 takeLatest 等 API 來監(jiān)聽某個 action 荠呐,當某個 action 觸發(fā)后邓厕, saga 可以使用 call 發(fā)起異步操作炫惩,操作完成后使用 put 函數(shù)觸發(fā) action ,同步更新 state 荔棉,從而完成整個 State 的更新闹炉。
  • ui->action1(異步請求)->redux-saga->action2->reducer(更新state)
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市润樱,隨后出現(xiàn)的幾起案子渣触,更是在濱河造成了極大的恐慌,老刑警劉巖壹若,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗅钻,死亡現(xiàn)場離奇詭異,居然都是意外死亡店展,警方通過查閱死者的電腦和手機养篓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赂蕴,“玉大人柳弄,你說我怎么就攤上這事。” “怎么了碧注?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵嚣伐,是天一觀的道長。 經(jīng)常有香客問我萍丐,道長轩端,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任逝变,我火速辦了婚禮基茵,結果婚禮上,老公的妹妹穿的比我還像新娘壳影。我一直安慰自己拱层,他們只是感情好,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布态贤。 她就那樣靜靜地躺著舱呻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悠汽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天芥驳,我揣著相機與錄音柿冲,去河邊找鬼。 笑死兆旬,一個胖子當著我的面吹牛假抄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播丽猬,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宿饱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了脚祟?” 一聲冷哼從身側(cè)響起谬以,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎由桌,沒想到半個月后为黎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡行您,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年铭乾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娃循。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡炕檩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捌斧,到底是詐尸還是另有隱情笛质,我是刑警寧澤泉沾,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站经瓷,受9級特大地震影響爆哑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜舆吮,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一揭朝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧色冀,春花似錦潭袱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至与学,卻和暖如春彤悔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背索守。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工晕窑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卵佛。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓杨赤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親截汪。 傳聞我的和親對象是個殘疾皇子疾牲,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355