翻译|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文件没讲,但是这里估计没有人会对这两个文件感兴趣.

开始进入正题吧

翻译|Redux Saga:hello,world!_第1张图片
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(  
 
   
 ,

这里我们导入一些依赖项(包括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 (
     

{ milliseconds }

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

通过使用connect高内聚组件,我们可以从store的state获取一个字段,并作为props传递进入组件.我们也通过一个对象传递四个action creators.Redux把这个对象绑定到store实例中,确保我们在组件中调用这几个action的时候,他们可以正确的被dispatch.
在我们的渲染中,我们返回一个

,这个元素中有一个SVG(后续中将会比较关键).SVG有一些事件操作句柄,这些操作句柄将会dispatch state修饰actions.接下来,会有一个

元素依据app的state来显示当前时间.最后我们有几个

你可能感兴趣的:(翻译|Redux Saga:hello,world!)