翻译|Redux Saga:hello,world!

12 OCTOBER 2016

这是翻译版本,原文请见


第一部分译文请见
第二部分译文请见
第三部分译文请见.


简单的Redux Saga 模板

在这个文章中,我们将完成完整的React/Redux/Redux Saga app,并且来看看为什么要这样做.

我已经创建了一个app的模板作为本文的起点,我们没有必要关注一些开发的细节,因为这些细节不是本系列文章的重点(我假设你已经了解React,Redux以及与此相关的开发工具.)但是我仍然会简单强调一些内容,以便于你对项目依赖包和配置有一些基础的了解.你可能是个高手,或者是个不折不扣的菜鸟(是菜鸟也没有关系)如果你不关心这些基础内容,直接跳到那副图片,看看后面的内容.

第一步,克隆repo,并且安装依赖包:

  //原文的repo不能运行了,下面的repo是验证过的
  git clone https://github.com/granmoe/redux-saga-clock-tutorial.git  
cd redux-saga-clock-tutorial  
npm i  

好了做完上面的工作,使用你喜欢的编辑器打开项目,让我们先看看里面有些什么内容.
在我们的package.json文件中每个元素都是非常标准的,但是要注意,如果要对付不支持ES2015标砖的浏览器,需要引入babel-polyfill包.这个包必须在redux-saga之前引入(译者:redux-saga使用了ES2015的技术,所以要先获得支持才可以).

你也可以注意到,在package.json中有ESLint依赖包,因为我发现这个依赖包是开发中的无价之宝.

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

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

我已经决定使用es2015,react和stage-2.

我还想讲讲.eslintrc文件,但是我实在是不想让你看到想睡觉.

webpack和index.html文件没讲,但是这里估计没有人会对这两个文件感兴趣.

开始进入正题吧

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>,

这里我们导入一些依赖项(包括babel-polyfill),导入根组件,redux store的配置,实例化store,然后在经过Provider class包装的”app”div的中使用ReactDOM渲染出根组件,这样以来,在app中所有组件树种的react组件都可以很容易的接入到我们的store实例.

查看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方法来创建middleware实例.接下来,把根reducer和middleware传递到createStore,这就创建了一个redux store.然后把我们app的root saga传递进saga middleware.这一步一定要在redux store实例化以后再执行.rootSaga是顶级generator,这个generator负责代理其他所有的generators的工作(马上会看到.)

上面都是些什么见鬼的代码,你个王八蛋(译者:原意直接翻译啊)

其实我们已经有了有趣的东西,我们的代码基本上依赖两个文件.”app.jsx”是一个react组件,可以根据app的state和基于DOM事件系统的actions来返回渲染的html标记.”duck.js”包含单纯对象actions和reducer,这两个函数一起工作描述出怎么修改state.其中也包含了所有的控制流代码,控制流代码描述了整个app的处理过程.如果你很熟悉标准的鸭子模型,我仅仅修改了鸭子模型,让他很容易包含saga代码.让我们使用鸭子模块来工作吧.

我们将会创建一个可以控制的时钟.开始来想想app需要的最精简的sate结构.在任何时间我们要询问app的状态是”现在几点了?”所有我们需要存储的就是单个的数字.现在让我们来看看怎么改变这个状态.好的,我们我们将制作一个时钟,用户可以向前,向后,暂停和重置.这里的构想意味着我们表征时间的代码逻辑需要增,减,什么也不做,重置到0.什么事也不做意味着不需要sate发生改变,所以我们留下增加,减少,重置.我们要显示时间的毫秒数,因此app的state就定为”毫秒数”.

正如上面所讲的,我们在redux代码中使用鸭子模型,如果你不喜欢这样做,可以分割成三个文件.
让我们看看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,接着有一个reducer,reducer实际上操作actions,它基于action type对state做出合适的修饰,之后创建新的state。最后我们export(模块模式)一些可以在其他地方调用的单纯action对象.(马上我们会在saga中导入action对象之一).示例代码总是这么这么的整洁.

现在我们需要实现一下app的流程.在处理过程中,什么状态需要输入?这个问题的另一个问法是:app在某个特定的时间应该做什么工作?我们的时钟可以向前,向后,暂停.为了在这几个过程中相互转变,我们需要三个action,开始时钟,拨回时间,暂停时钟.

从代码//saga actions开始,看看duck模块的剩余部分.我们已经创建了三个actions,我们的root saga在收到某个action的时候,会启动一个傻瓜处理流程.现在在代码里傻瓜处理流程只是打印一下action的名字.后续我们会开始根据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(严格上讲,根据术语来说应该是叫“action creators”,但是无所谓,只要你理解具体的意义就可以)应该看起来和其他的redux actions类似.但是这些action在我们的reducer中不能得到处理.如果保持仅仅在saga代码附近保留这些actions,这里的acions仅仅触发saga.做到这一点,会避免action和根据这些action做出的state修改的代码混杂在一起.显而易见,saga action仍然通脱connect函数绑定到store实例,并且输入到组件里.

现在解释一下这个文件里奇怪的saga.你还记得rootSaga被传递到saga中间件,对吗?坦率讲,你可能也不知道,但是这也没关系.每次我们发出一个action,action会被推送到经过sagaMiddleware.run(generator)包装的generator.这就意味着,每个generator都有机会响应action,在我们的实例中,rootSaga遇到匹配的action type的时候才会做出响应.我们正在使用从Redux Saga获取的takeLatest助手函数完成这个工作.takeLatest接收任何与action type数组匹配的action,然后接着传递他,启动一个handleClockAcion流程,传递进action.takeLatest意思是直接收最新的action,如果现在还有正在运行的handleClockAction的话,在新的action开始之前,当前的这个处理流程需要先退出.handleClockAction,本质上是在后台启动,允许rootSaga保持运行状态,即使handleClockAction仍在运行,也可以接受下一个匹配的action.

注意我们使用的yield关键词,回想一下,yield在generator中发出和接收值.在任何时间,我们yield一个Redux Saga助手或者effect的时候,我们就正在和Saga middleware进行通讯.在我们的上面的实例中,Redux Saga等待匹配发送到saga的action.后面我们还会更进一步深入讨论.
我希望你至少对这个流程有一点感觉.我认为可能在测试过程中(译者:这里的意思是实际运行代码的过程,并不是代码的测试过程)你对这个流程更清楚一点.所以让我们看看React组件中怎么和用户进行交互的过程.
在组件这一点看,“app.jsx”是非常简单的react组件.让我们看个仔细.

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高内聚组件,我们可以从store的state获取一个字段,并作为props传递进入组件.我们也通过一个对象传递四个action creators.Redux把这个对象绑定到store实例中,确保我们在组件中调用这几个action的时候,他们可以正确的被dispatch.
在我们的渲染中,我们返回一个<div>,这个元素中有一个SVG(后续中将会比较关键).SVG有一些事件操作句柄,这些操作句柄将会dispatch state修饰actions.接下来,会有一个<p>元素依据app的state来显示当前时间.最后我们有几个<button>s 连接到saga的actions.
上面的代码都就位以后,我们就试着运行一下app,验证一下基础构架和actions的工作情况.

到底能工作吗?

回到你的终端,运行npm start.现在输入localhost:8080,在浏览器中打开devtools,检查一下js 终端.当你点击buttons的时候,会看到saga actions的日志输出.现在试着在SVG上点击,鼠标一定,双击action.你可以看到毫秒文本的更新.

app的界面

真好啊,我们创建了一个Redux Saga app的模板结构,了解了怎么使用takeLatest.还可以在终端中输出一些日志信息.棒!
在下一篇文章中,我们会完成整个时钟的实施,得到一些非常酷的内容.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容