翻译|Redux Saga:hello,world!

作者: smartphp | 来源:发表于2017-03-13 09:26 被阅读427次

    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.还可以在终端中输出一些日志信息.棒!
    在下一篇文章中,我们会完成整个时钟的实施,得到一些非常酷的内容.

    相关文章

      网友评论

        本文标题:翻译|Redux Saga:hello,world!

        本文链接:https://www.haomeiwen.com/subject/mykfnttx.html