翻譯|Redux Saga:hello,world!

12 OCTOBER 2016

這是翻譯版本,原文請(qǐng)見


第一部分譯文請(qǐng)見,
第二部分譯文請(qǐng)見,
第三部分譯文請(qǐng)見.


簡單的Redux Saga 模板

在這個(gè)文章中,我們將完成完整的React/Redux/Redux Saga app,并且來看看為什么要這樣做.

我已經(jīng)創(chuàng)建了一個(gè)app的模板作為本文的起點(diǎn),我們沒有必要關(guān)注一些開發(fā)的細(xì)節(jié),因?yàn)檫@些細(xì)節(jié)不是本系列文章的重點(diǎn)(我假設(shè)你已經(jīng)了解React,Redux以及與此相關(guān)的開發(fā)工具.)但是我仍然會(huì)簡單強(qiáng)調(diào)一些內(nèi)容,以便于你對(duì)項(xiàng)目依賴包和配置有一些基礎(chǔ)的了解.你可能是個(gè)高手,或者是個(gè)不折不扣的菜鳥(是菜鳥也沒有關(guān)系)如果你不關(guān)心這些基礎(chǔ)內(nèi)容,直接跳到那副圖片,看看后面的內(nèi)容.

第一步,克隆repo,并且安裝依賴包:

  //原文的repo不能運(yùn)行了,下面的repo是驗(yàn)證過的
  git clone https://github.com/granmoe/redux-saga-clock-tutorial.git  
cd redux-saga-clock-tutorial  
npm i  

好了做完上面的工作,使用你喜歡的編輯器打開項(xiàng)目,讓我們先看看里面有些什么內(nèi)容.
在我們的package.json文件中每個(gè)元素都是非常標(biāo)準(zhǔn)的,但是要注意,如果要對(duì)付不支持ES2015標(biāo)磚的瀏覽器,需要引入babel-polyfill包.這個(gè)包必須在redux-saga之前引入(譯者:redux-saga使用了ES2015的技術(shù),所以要先獲得支持才可以).

你也可以注意到,在package.json中有ESLint依賴包,因?yàn)槲野l(fā)現(xiàn)這個(gè)依賴包是開發(fā)中的無價(jià)之寶.

下面是我們的babel配置,在.babelrc文件中:

  {
  "presets": ["es2015", "react", "stage-2"]
  }

我已經(jīng)決定使用es2015,react和stage-2.

我還想講講.eslintrc文件,但是我實(shí)在是不想讓你看到想睡覺.

webpack和index.html文件沒講,但是這里估計(jì)沒有人會(huì)對(duì)這兩個(gè)文件感興趣.

開始進(jìn)入正題吧

patrick Stewart McKellan Elmo.jpg

app的入口文件是main.jsx:

  import 'babel-polyfill' // generator support  
import React from 'react'  
import ReactDOM from 'react-dom'  
import { Provider } from 'react-redux'

import App from 'app.jsx'  
import initStore from 'store'

const store = initStore()

ReactDOM.render(  
 <Provider store={ store }>
   <App />
 </Provider>,

這里我們導(dǎo)入一些依賴項(xiàng)(包括babel-polyfill),導(dǎo)入根組件,redux store的配置,實(shí)例化store,然后在經(jīng)過Provider class包裝的”app”div的中使用ReactDOM渲染出根組件,這樣以來,在app中所有組件樹種的react組件都可以很容易的接入到我們的store實(shí)例.

查看store.js,代碼中我們使用saga middleware來配置我們的store:

import createSagaMiddleware from 'redux-saga'

export default function () {  
 const sagaMiddleware = createSagaMiddleware()

 const store = createStore(
   rootReducer,
   applyMiddleware(sagaMiddleware)
 )

 sagaMiddleware.run(rootSaga)

 return store
}

首先我們使用createSagaMiddleware方法來創(chuàng)建middleware實(shí)例.接下來,把根reducer和middleware傳遞到createStore,這就創(chuàng)建了一個(gè)redux store.然后把我們app的root saga傳遞進(jìn)saga middleware.這一步一定要在redux store實(shí)例化以后再執(zhí)行.rootSaga是頂級(jí)generator,這個(gè)generator負(fù)責(zé)代理其他所有的generators的工作(馬上會(huì)看到.)

上面都是些什么見鬼的代碼,你個(gè)王八蛋(譯者:原意直接翻譯啊)

其實(shí)我們已經(jīng)有了有趣的東西,我們的代碼基本上依賴兩個(gè)文件.”app.jsx”是一個(gè)react組件,可以根據(jù)app的state和基于DOM事件系統(tǒng)的actions來返回渲染的html標(biāo)記.”duck.js”包含單純對(duì)象actions和reducer,這兩個(gè)函數(shù)一起工作描述出怎么修改state.其中也包含了所有的控制流代碼,控制流代碼描述了整個(gè)app的處理過程.如果你很熟悉標(biāo)準(zhǔn)的鴨子模型,我僅僅修改了鴨子模型,讓他很容易包含saga代碼.讓我們使用鴨子模塊來工作吧.

我們將會(huì)創(chuàng)建一個(gè)可以控制的時(shí)鐘.開始來想想app需要的最精簡的sate結(jié)構(gòu).在任何時(shí)間我們要詢問app的狀態(tài)是”現(xiàn)在幾點(diǎn)了?”所有我們需要存儲(chǔ)的就是單個(gè)的數(shù)字.現(xiàn)在讓我們來看看怎么改變這個(gè)狀態(tài).好的,我們我們將制作一個(gè)時(shí)鐘,用戶可以向前,向后,暫停和重置.這里的構(gòu)想意味著我們表征時(shí)間的代碼邏輯需要增,減,什么也不做,重置到0.什么事也不做意味著不需要sate發(fā)生改變,所以我們留下增加,減少,重置.我們要顯示時(shí)間的毫秒數(shù),因此app的state就定為”毫秒數(shù)”.

正如上面所講的,我們?cè)趓edux代碼中使用鴨子模型,如果你不喜歡這樣做,可以分割成三個(gè)文件.
讓我們看看duck.js中的第一部分,saga actions.

 import { takeLatest } from 'redux-saga'
const initialState = {  
 milliseconds: 0
}

export default function reducer (currentState = initialState, action) {  
 switch (action.type) {
   case 'reset-clock':
     return {
       ...currentState,
       milliseconds: 0
     }
   case 'increment-milliseconds':
     return {
       ...currentState,
       milliseconds: currentState.milliseconds + 100
     }
   case 'decrement-milliseconds':
     if (!currentState.milliseconds) { return currentState }
     return {
       ...currentState,
       milliseconds: currentState.milliseconds - 100
     }
   default:
     return currentState
 }
}

export const resetClock = () => ({ type: 'reset-clock' }) 

export const incrementMilliseconds = () => ({ type: 'increment-milliseconds' })  

export const decrementMilliseconds = () => ({ type: 'decrement-milliseconds' }) 

上面這段代碼很簡單.首先由我們需求字段的起始state,接著有一個(gè)reducer,reducer實(shí)際上操作actions,它基于action type對(duì)state做出合適的修飾,之后創(chuàng)建新的state。最后我們export(模塊模式)一些可以在其他地方調(diào)用的單純action對(duì)象.(馬上我們會(huì)在saga中導(dǎo)入action對(duì)象之一).示例代碼總是這么這么的整潔.

現(xiàn)在我們需要實(shí)現(xiàn)一下app的流程.在處理過程中,什么狀態(tài)需要輸入?這個(gè)問題的另一個(gè)問法是:app在某個(gè)特定的時(shí)間應(yīng)該做什么工作?我們的時(shí)鐘可以向前,向后,暫停.為了在這幾個(gè)過程中相互轉(zhuǎn)變,我們需要三個(gè)action,開始時(shí)鐘,撥回時(shí)間,暫停時(shí)鐘.

從代碼//saga actions開始,看看duck模塊的剩余部分.我們已經(jīng)創(chuàng)建了三個(gè)actions,我們的root saga在收到某個(gè)action的時(shí)候,會(huì)啟動(dòng)一個(gè)傻瓜處理流程.現(xiàn)在在代碼里傻瓜處理流程只是打印一下action的名字.后續(xù)我們會(huì)開始根據(jù)action type處理具體的增,減,休眠流程.這里是duck.js的saga代碼.

 // saga actions
export const startClock = () => ({ type: 'start-clock' })  
export const pauseClock = () => ({ type: 'pause-clock' })  
export const rewindClock = () => ({ type: 'rewind-clock' })

// saga
export function* rootSaga () {  
 yield takeLatest(['start-clock', 'pause-clock', 'rewind-clock'], handleClockAction)
}

function* handleClockAction ({ type }) {  
 console.log('Pushed this action to handleClockAction: ', type)
}

actions(嚴(yán)格上講,根據(jù)術(shù)語來說應(yīng)該是叫“action creators”,但是無所謂,只要你理解具體的意義就可以)應(yīng)該看起來和其他的redux actions類似.但是這些action在我們的reducer中不能得到處理.如果保持僅僅在saga代碼附近保留這些actions,這里的acions僅僅觸發(fā)saga.做到這一點(diǎn),會(huì)避免action和根據(jù)這些action做出的state修改的代碼混雜在一起.顯而易見,saga action仍然通脫connect函數(shù)綁定到store實(shí)例,并且輸入到組件里.

現(xiàn)在解釋一下這個(gè)文件里奇怪的saga.你還記得rootSaga被傳遞到saga中間件,對(duì)嗎?坦率講,你可能也不知道,但是這也沒關(guān)系.每次我們發(fā)出一個(gè)action,action會(huì)被推送到經(jīng)過sagaMiddleware.run(generator)包裝的generator.這就意味著,每個(gè)generator都有機(jī)會(huì)響應(yīng)action,在我們的實(shí)例中,rootSaga遇到匹配的action type的時(shí)候才會(huì)做出響應(yīng).我們正在使用從Redux Saga獲取的takeLatest助手函數(shù)完成這個(gè)工作.takeLatest接收任何與action type數(shù)組匹配的action,然后接著傳遞他,啟動(dòng)一個(gè)handleClockAcion流程,傳遞進(jìn)action.takeLatest意思是直接收最新的action,如果現(xiàn)在還有正在運(yùn)行的handleClockAction的話,在新的action開始之前,當(dāng)前的這個(gè)處理流程需要先退出.handleClockAction,本質(zhì)上是在后臺(tái)啟動(dòng),允許rootSaga保持運(yùn)行狀態(tài),即使handleClockAction仍在運(yùn)行,也可以接受下一個(gè)匹配的action.

注意我們使用的yield關(guān)鍵詞,回想一下,yield在generator中發(fā)出和接收值.在任何時(shí)間,我們yield一個(gè)Redux Saga助手或者effect的時(shí)候,我們就正在和Saga middleware進(jìn)行通訊.在我們的上面的實(shí)例中,Redux Saga等待匹配發(fā)送到saga的action.后面我們還會(huì)更進(jìn)一步深入討論.
我希望你至少對(duì)這個(gè)流程有一點(diǎn)感覺.我認(rèn)為可能在測試過程中(譯者:這里的意思是實(shí)際運(yùn)行代碼的過程,并不是代碼的測試過程)你對(duì)這個(gè)流程更清楚一點(diǎn).所以讓我們看看React組件中怎么和用戶進(jìn)行交互的過程.
在組件這一點(diǎn)看,“app.jsx”是非常簡單的react組件.讓我們看個(gè)仔細(xì).

import React from 'react'  
import { connect } from 'react-redux'

import { incrementMilliseconds, decrementMilliseconds, resetClock, startClock, pauseClock, rewindClock } from 'duck'

class Clock extends React.Component {  
 render () {
   const {
     milliseconds,
     incrementMilliseconds,
     decrementMilliseconds,
     resetClock,
     startClock,
     pauseClock,
     rewindClock
   } = this.props

   
   return (
     <div>
       <svg onClick={ incrementMilliseconds } onDoubleClick={ resetClock } onMouseLeave={ decrementMilliseconds }
         className="clock" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="500">
         <circle cx="50" cy="50" r={ 30 } stroke={ 'rgba(1,1,1,1)' } fill="orange" />
       </svg>
       <p>{ milliseconds }</p>
       <p>
         <button type="button" onClick={ startClock }>Start Clock</button>
         <button type="button" onClick={ pauseClock }>Pause Clock</button>
         <button type="button" onClick={ rewindClock }>Rewind Clock</button>
       </p>
     </div>
   )
 }
}

export default connect(state => ({  
 milliseconds: state.milliseconds
}), ({
 incrementMilliseconds,
 decrementMilliseconds,
 resetClock,
 startClock,
 pauseClock,
 rewindClock
}))(Clock)
  

通過使用connect高內(nèi)聚組件,我們可以從store的state獲取一個(gè)字段,并作為props傳遞進(jìn)入組件.我們也通過一個(gè)對(duì)象傳遞四個(gè)action creators.Redux把這個(gè)對(duì)象綁定到store實(shí)例中,確保我們?cè)诮M件中調(diào)用這幾個(gè)action的時(shí)候,他們可以正確的被dispatch.
在我們的渲染中,我們返回一個(gè)<div>,這個(gè)元素中有一個(gè)SVG(后續(xù)中將會(huì)比較關(guān)鍵).SVG有一些事件操作句柄,這些操作句柄將會(huì)dispatch state修飾actions.接下來,會(huì)有一個(gè)<p>元素依據(jù)app的state來顯示當(dāng)前時(shí)間.最后我們有幾個(gè)<button>s 連接到saga的actions.
上面的代碼都就位以后,我們就試著運(yùn)行一下app,驗(yàn)證一下基礎(chǔ)構(gòu)架和actions的工作情況.

到底能工作嗎?

回到你的終端,運(yùn)行npm start.現(xiàn)在輸入localhost:8080,在瀏覽器中打開devtools,檢查一下js 終端.當(dāng)你點(diǎn)擊buttons的時(shí)候,會(huì)看到saga actions的日志輸出.現(xiàn)在試著在SVG上點(diǎn)擊,鼠標(biāo)一定,雙擊action.你可以看到毫秒文本的更新.

app的界面

真好啊,我們創(chuàng)建了一個(gè)Redux Saga app的模板結(jié)構(gòu),了解了怎么使用takeLatest.還可以在終端中輸出一些日志信息.棒!
在下一篇文章中,我們會(huì)完成整個(gè)時(shí)鐘的實(shí)施,得到一些非??岬膬?nèi)容.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,672評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,690評(píng)論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,438評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,667評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,845評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評(píng)論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,384評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

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