-
Notifications
You must be signed in to change notification settings - Fork 225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
彻彻底底教会你使用Redux-saga(包含样例代码) #14
Comments
写的很不错 |
谢谢哈 |
👍 |
写的很详细,女生就是细腻一点 |
谢谢表扬哈,去了一家小公司哈 |
写的很清晰易懂👍 有一个小问题, 为什么要用saga 来 watch用户输入账户和密码呢?这个过程没有异步操作, 可以直接被reducer捕捉到。 |
是的,确实不需要用saga |
你好,使用saga我有一个问题:
我在UI中调用 action 触发异步请求,例子中成功后我可以修改 store。 |
首先先接受一个action,通过redux-saga中的take,然后通过redux-saga中的call 方法调用一个请求函数 yield call( ...fetchURL,params) , yield会等待call方法返回结果后再往下执行,调用yield call请求后,你就可以alert('请求结束') , 如果不想用window.alert(), 你可以在store中保存alert是否显示的变量,在yield call后,改变这个变量的值,使得自定义的alert弹窗显示 |
@forthealllight |
想请教一下,为什么实例代码中的 generator 函数里面要写 while(true) 来包裹逻辑?
|
您好,看到你的问题。第一时间来回答你,因为type = ‘CHANGE_USERNAME’ 动作不可能只发生一次。 当然,你如果只需要监听一次动作, 那么就不需要while循环。 ~ |
有些明白了,非常感谢帮助! |
不客气~ |
这儿应该是 顺便,感谢 blog 的帮助-.- |
有几点疑问想请教一下:
|
感谢指正,已经修改 |
1、UI在请求后如何处理,一般是这样子,在saga中请求,请求完成后更新store中的state,而UI-components从store中取新的state,以props的形式传递,从而更新整个视图。这里跟你用redux-thunk一样的,仅仅是一个异步的中间件。本质还是redux做状态管理。 2、我知道你说所的可能是无效render。在设计redux树的时候,可以选择完全弃用局部的state,将所有状态保存在store中,这种情况不会有性能问题,可以通过SCU来控制渲染实现你所说的优化。当然,有些也不需要完全弃用局部state,你这个问题本质是一个如何设计redux状态树的问题。 总结: 1、2 两个问题其实本质跟redux-saga无关,是redux的问题 3、可以理解成 whille(true){ 等价于 const action yield takeEvery(someaction); 本质上takeEvery是根据take来实现的一个上层的API。 最后,希望能对你有所帮助~ |
@forthealllight 感谢解答,很有帮助~ |
还有一个补充,takeEvery虽然是一个顶层API,但是他是action发起时,被push到任务执行器然后执行的。而用底层API take的好处,是去pull新任务,然后执行。 两者的却别就是使用take可以实现更复杂的控制逻辑,比如使用takeEvery无法结束监听,而底层的take就很方便。 简单来说,可以用一句话概括:底层的API,实现细节更加可控哦~ 不用谢 |
你好,有个问题请教下。看了下你另一个仓库saga-example中的例子,因为是只有登录和获取列表的异步,所以saga的相关代码是放在src/saga/index.js中。 |
当然可以,这个就是我所说的redux-dark模式。你可以参考我的这篇文章:#41 |
@forthealllight 谢谢。我尝试下你所说的 |
小姐姐你好,请问下实际业务中,saga.js这个文件怎么拆分呢,不可能不同业务几十个函数全放一个文件 |
我是按页面拆分的,一个页面对应一个saga文件,然后在根目录require到一起的哦,具体可以看我的这篇文章#41 |
那这样的话,reducer的状态会不会被拆的太散了,比如 2个页面可能复用一个reducer。 |
保持一个页面一个reducer的拆分 |
感谢小姐姐,学习了 |
目前硬要我挑个毛病的话,redux-thunk的缺点下面的单词少了个t |
Redux-saga使用心得总结(包含样例代码),本文的样例代码地址:样例代码地址 ,欢迎star
最近将项目中redux的中间件,从redux-thunk替换成了redux-saga,做个笔记总结一下redux-saga的使用心得,阅读本文需要了解什么是redux,redux中间件的用处是什么?如果弄懂上述两个概念,就可以继续阅读本文。
1.redux-thunk处理副作用的缺点
(1)redux的副作用处理
redux中的数据流大致是:
UI—————>action(plain)—————>reducer——————>state——————>UI
redux是遵循函数式编程的规则,上述的数据流中,action是一个原始js对象(plain object)且reducer是一个纯函数,对于同步且没有副作用的操作,上述的数据流起到可以管理数据,从而控制视图层更新的目的。
但是如果存在副作用,比如ajax异步请求等等,那么应该怎么做?
如果存在副作用函数,那么我们需要首先处理副作用函数,然后生成原始的js对象。如何处理副作用操作,在redux中选择在发出action,到reducer处理函数之间使用中间件处理副作用。
redux增加中间件处理副作用后的数据流大致如下:
UI——>action(side function)—>middleware—>action(plain)—>reducer—>state—>UI
在有副作用的action和原始的action之间增加中间件处理,从图中我们也可以看出,中间件的作用就是:
转换异步操作,生成原始的action,这样,reducer函数就能处理相应的action,从而改变state,更新UI。
(2)redux-thunk
在redux中,thunk是redux作者给出的中间件,实现极为简单,10多行代码:
这几行代码做的事情也很简单,判别action的类型,如果action是函数,就调用这个函数,调用的步骤为:
发现实参为dispatch和getState,因此我们在定义action为thunk函数是,一般形参为dispatch和getState。
(3)redux-thunk的缺点
hunk的缺点也是很明显的,thunk仅仅做了执行这个函数,并不在乎函数主体内是什么,也就是说thunk使
得redux可以接受函数作为action,但是函数的内部可以多种多样。比如下面是一个获取商品列表的异步操作所对应的action:
从这个具有副作用的action中,我们可以看出,函数内部极为复杂。如果需要为每一个异步操作都如此定义一个action,显然action不易维护。
action不易维护的原因:
2.redux-saga写一个hellosaga
跟redux-thunk不同的是,redux-saga是控制执行的generator,在redux-saga中action是原始的js对象,把所有的异步副作用操作放在了saga函数里面。这样既统一了action的形式,又使得异步操作集中可以被集中处理。
redux-saga是通过genetator实现的,如果不支持generator需要通过插件babel-polyfill转义。我们接着来实现一个输出hellosaga的例子。
(1)创建一个helloSaga.js文件
(2)在redux中使用redux-saga中间件
在main.js中:
和调用redux的其他中间件一样,如果想使用redux-saga中间件,那么只要在applyMiddleware中调用一个createSagaMiddleware的实例。唯一不同的是需要调用run方法使得generator可以开始执行。
3.redux-saga的使用技术细节
redux-saga除了上述的action统一、可以集中处理异步操作等优点外,redux-saga中使用声明式的Effect以及提供了更加细腻的控制流。
(1)声明式的Effect
redux-saga中最大的特点就是提供了声明式的Effect,声明式的Effect使得redux-saga监听原始js对象形式的action,并且可以方便单元测试,我们一一来看。
首先来看redux-thunk的大体过程:
action1(side function)—>redux-thunk监听—>执行相应的有副作用的方法—>action2(plain object)
转化到action2是一个原始js对象形式的action,然后执行reducer函数就会更新store中的state。
而redux-saga的大体过程如下:
action1(plain object)——>redux-saga监听—>执行相应的Effect方法——>返回描述对象—>恢复执行异步和副作用函数—>action2(plain object)
对比redux-thunk我们发现,redux-saga中监听到了原始js对象action,并不会马上执行副作用操作,会先通过Effect方法将其转化成一个描述对象,然后再将描述对象,作为标识,再恢复执行副作用函数。
通过使用Effect类函数,可以方便单元测试,我们不需要测试副作用函数的返回结果。只需要比较执行Effect方法后返回的描述对象,与我们所期望的描述对象是否相同即可。
举例来说,call方法是一个Effect类方法:
上述代码中,比如我们需要测试Api.fetch返回的结果是否符合预期,通过调用call方法,返回一个描述对象。这个描述对象包含了所需要调用的方法和执行方法时的实际参数,我们认为只要描述对象相同,也就是说只要调用的方法和执行该方法时的实际参数相同,就认为最后执行的结果肯定是满足预期的,这样可以方便的进行单元测试,不需要模拟Api.fetch函数的具体返回结果。
(2)Effect提供的具体方法
下面来介绍几个Effect中常用的几个方法,从低阶的API,比如take,call(apply),fork,put,select等,以及高阶API,比如takeEvery和takeLatest等,从而加深对redux-saga用法的认识(这节可能比较生涩,在第三章中会结合具体的实例来分析,本小节先对各种Effect有一个初步的了解)。
引入:
take这个方法,是用来监听action,返回的是监听到的action对象。比如:
在UI Component中dispatch一个action:
在saga中使用:
可以监听到UI传递到中间件的Action,上述take方法的返回,就是dipath的原始对象。一旦监听到login动作,返回的action为:
call和apply方法与js中的call和apply相似,我们以call方法为例:
call方法调用fn,参数为args,返回一个描述对象。不过这里call方法传入的函数fn可以是普通函数,也可以是generator。call方法应用很广泛,在redux-saga中使用异步请求等常用call方法来实现。
在前面提到,redux-saga做为中间件,工作流是这样的:
UI——>action1————>redux-saga中间件————>action2————>reducer..
从工作流中,我们发现redux-saga执行完副作用函数后,必须发出action,然后这个action被reducer监听,从而达到更新state的目的。相应的这里的put对应与redux中的dispatch,工作流程图如下:
从图中可以看出redux-saga执行副作用方法转化action时,put这个Effect方法跟redux原始的dispatch相似,都是可以发出action,且发出的action都会被reducer监听到。put的使用方法:
put方法与redux中的dispatch相对应,同样的如果我们想在中间件中获取state,那么需要使用select。select方法对应的是redux中的getState,用户获取store中的state,使用方法:
fork方法在第三章的实例中会详细的介绍,这里先提一笔,fork方法相当于web work,fork方法不会阻塞主线程,在非阻塞调用中十分有用。
takeEvery和takeLatest用于监听相应的动作并执行相应的方法,是构建在take和fork上面的高阶api,比如要监听login动作,好用takeEvery方法可以:
takeEvery监听到login的动作,就会执行loginFunc方法,除此之外,takeEvery可以同时监听到多个相同的action。
takeLatest方法跟takeEvery是相同方式调用:
与takeLatest不同的是,takeLatest是会监听执行最近的那个被触发的action。
4.redux-saga实现一个登陆和列表样例
接着我们来实现一个redux-saga样例,存在一个登陆页,登陆成功后,显示列表页,并且,在列表页,可
以点击登出,返回到登陆页。例子的最终展示效果如下:
样例的功能流程图为:
接着我们按照上述的流程来一步步的实现所对应的功能。
(1)LoginPanel(登陆页)
登陆页的功能包括
I)输入时时保存用户名和密码
用户名输入框和密码框onchange时触发的函数为:
在函数中最后会dispatch两个action:CHANGE_USERNAME和CHANGE_PASSWORD。
在saga.js文件中监听这两个方法并执行副作用函数,最后put发出转化后的action,给reducer函数调用:
最后在reducer中接收到redux-saga的put方法传递过来的action:change_username和change_password,然后更新state。
II)监听登陆事件判断登陆是否成功
在UI中发出的登陆事件为:
登陆事件的action为:TO_LOGIN_IN.对于登入事件的处理函数为:
在上述的处理函数中,首先监听原始动作提取出传递来的用户名和密码,然后请求是否登陆成功,如果登陆成功有返回值,则执行put的action:to_login_in.
(2) LoginSuccess(登陆成功列表展示页)
登陆成功后的页面功能包括:
I)获取列表信息
为了演示请求过程,我们在本地mock,通过redux-saga的工具函数delay,delay的功能相当于延迟xx秒,因为真实的请求存在延迟,因此可以用delay在本地模拟真实场景下的请求延迟。
II)登出功能
与登入相似,登出的功能从UI处接受action:TO_LOGIN_OUT,然后转发action:to_login_out
(3) 完整的实现登入登出和列表展示的代码
通过请求状态码判断登入是否成功,在登陆成功后,可以通过:
的方式调用获取活动列表的函数getList。这样咋一看没有什么问题,但是注意call方法调用是会阻塞主线程的,具体来说:
在call方法调用结束之前,call方法之后的语句是无法执行的
如果call(getList)存在延迟,call(getList)之后的语句 const action2=yieldtake('TO_LOGIN_OUT')在call方法返回结果之前无法执行
在延迟期间的登出操作会被忽略。
用框图可以更清楚的分析:
call方法调用阻塞主线程的具体效果如下动图所示:
白屏时为请求列表的等待时间,在此时,我们点击登出按钮,无法响应登出功能,直到请求列表成功,展示列表信息后,点击登出按钮才有相应的登出功能。也就是说call方法阻塞了主线程。
(4) 无阻塞调用
我们在第二章中,介绍了fork方法可以类似与web work,fork方法不会阻塞主线程。应用于上述例子,我们可以将:
修改为:
这样展示的结果为:
通过fork方法不会阻塞主线程,在白屏时点击登出,可以立刻响应登出功能,从而返回登陆页面。
5.总结
通过上述章节,我们可以概括出redux-saga做为redux中间件的全部优点:
统一action的形式,在redux-saga中,从UI中dispatch的action为原始对象
集中处理异步等存在副作用的逻辑
通过转化effects函数,可以方便进行单元测试
完善和严谨的流程控制,可以较为清晰的控制复杂的逻辑。
The text was updated successfully, but these errors were encountered: