redux-saga或redux-thunk到底解决了什么问题?



  • 项目中也在使用redux-saga,因为其繁琐的写法,导致对其有很大的抵触心理。
    目前的做法是如果不需要共享的数据,直接在所在组件中请求使用,尽量避免写redux和saga代码。
    而且,也一直在思考,即使需要共享数据,也可以不使用中间库来解决,比如:
    现在有两个页面需要共享同一个请求的数据,我可以将此请求写成一个服务service.js,将请求的过程及dispatch的逻辑放入到此服务中,根据请求的返回值判断需要dispatch哪个action就ok了。

    class Service {
      async create() {
        const {success, data, message} = await http.get('test/api');
        if (success) {
          store.dispatch({type: "FETCH_SUCCEEDED", data})
        } else {
          store.dispatch({type: "FETCH_FAILED", message})
        }
      }
    }
    

    不知道我这样理解对不对,还请大佬指点@晴明



  • 没办法给你解释, 你这个还需要转很大的弯,才能明白是怎么回事.

    我现在只能给你一个建议, 不要胡子眉毛一把抓.你思考一下你这个应用中有哪些独立的逻辑流程, 比如电商APP, 用户注册,登录,改密码这是一个独立的流程. 展示货物是一个独立的流程, 购物是一个独立的流程. 拿出用户的这个独立流程, 如果用户输入了密码, 频繁点击登录按钮,你应该怎么处理? 等等非常细节的问题,Redux-saga都给你想到了. 所以要使用Redux-Saga,你首先要对自己的应用的细节要有充分的认识, 这时候你就知道了,Redux-Saga给你的逻辑处理提供了玩呗的可调用方法.

    你上面这段代码违反了Redux,甚至是React的原则,就是数据是单向流动的.
    实际Redux就是 你整个APP的父组件, 在Redux中,数据只能从Redux流行APP组件.

    如果你非要这么写,而且组件之间也要共享数据集, 你可以使用React-Hooks的方法, 从服务获取数据可以用useAxios,useAsync,use-react-hooks等方法,如果要共享数据可以用useContext的方法来共享数据. 还要一些hooks也可以使用. 这些你要自己去找了.



  • @phpsmarter214
    谢谢回复。
    一. 你说的saga是有许多比较高级的功能,takeLatest,fork,race等其他功能,这些我是知道的,但是这些功能目前确实比较少用,或压根用不上。
    就是说如果仅从解决异步请求的点出发的话使用saga是不是有点多余。
    二. 我上面的示例代码可能不太符合redux的规范,但是我的理解是用户点击按钮调用Service.create(),然后根据返回值触发action和用户点击按钮直接触发action没有什么区别,感觉这样也不会带来什么问题,这块确实很困惑。



  • Redux-Saga的基本思想是把app的中相对独立的逻辑打包起来处理.这一点是Redux-Saga的着眼点. Redux-Saga是Redux的一个中间件,我们在UI中dispatch一个动作, 这个动作会触发对应的state变化,依据是action的type, 可以粗略的认为,Redux-Saga为一类相对独立于其他部分的逻辑又添加一个了一个更大的type.
    比如用户登录触发操作, 与之相关的必然是登出操作,把这些部分打包,就比较自然了.



  • @phpsmarter214
    恩,你说的我明白,对saga也使用了很长时间了,他的高级功能也都了解过,但是依然无法消除我的上述疑问。再次感谢。



  • @yoonzm
    是的,简单的异步请求用redux-thunk就可以了, 在请求完成之后,可以在dispatch新的动作.

    这里是你对Redux的核心,尤其是reducer的功能没有理解. 在有Redux的应用中,
    Redux负责数据的管理和储存, 用户点击按钮dispatch的动作实际是触发Redux中定义的方法, 你要知道在组件中我们dispatch的只是函数名和参数. 因此我们在组件中dispatch的函数实际触发的是Redux中的函数,没有在组件中调用函数.
    React的组件只做了一件事就是展示数据. 在组件中dispatch一个"函数名",并没有在组件中调用函数,只是通知Redux,按照这个函数的名字来执行操作.

    这个地方的弯不太好转.



  • @yoonzm
    看你写的那个 Service的类, 你现在要加深Redux的理解.否则Redux-Saga用起来会比较痛苦.



  • 0_1556030983189_da7c6062-71a6-4aec-9b95-0f29905814e6-image.png
    0_1556031021767_54cb8458-5677-46d1-99fd-9b69c5f0b814-image.png

    redux-thunk或者saga就是为了解决有操作无状态的副作用

    如你上面所说,另外做一个service也能解决问题,那么saga还有额外的好处
    一个是有一些很炫的高级功能,另一个一般可能体会不到的好处是,saga仍然利用redux的流程,用action描述意图,解耦逻辑代码,便于阅读、重构和写单元测试

    0_1556031345829_63a3c4df-9dad-4bf9-a53e-ae8319c42597-image.png

    我举几个例子:
    0_1556031358337_eda0c2f4-4ea7-46ab-b058-333a63503645-image.png
    0_1556031368669_bdc57976-7dc9-41db-a8f9-91277d0b0e30-image.png

    0_1556031537898_7f46d401-07dd-426e-ad2e-5fadf960464c-image.png
    0_1556031545645_8fc3f801-3e26-43da-b37e-e8ae2a70e31b-image.png

    很容易看出上面这些代码之间非常便于移动、重构,不同模块间也便于并行分工(组件只管抛出要做什么样的action)。saga代码直接删掉都不会引起报错。

    此外在写单元测试时,对于组件来说,我只管我抛出的action对不对,参数对不对。而saga只管我call或者fork的方法名字对不对得上,完全不用去管互相之间怎么调用,不需要特意去mock,堪称单元测试思想的典范。



  • @晴明 @phpsmarter214
    感谢两位的耐心解答,我仔细想了下,如果把例子中的service.js改成

    class Service {
      async create() {
        store.dispatch({type: "FETCH_START"});
        const {success, data, message} = await http.get('test/api');
        if (success) {
          store.dispatch({type: "FETCH_SUCCEEDED", data})
        } else {
          store.dispatch({type: "FETCH_FAILED", message})
        }
      }
    }
    

    这似乎就是redux-thunk了。
    然后@晴明 说的我也感受了,使用saga后的解耦确实在重构的时候会特别爽,即使整个页面需要重新做,也不需要去重新需改saga的逻辑,也可以完全不用关心action后面的逻辑是什么样的,也就是说完全把数据以及逻辑和视图分开了。
    还有一点确实也很重要,就是单元测试,可能因为目前还没有使用自动化测试的缘故,没有体会到使用的便利。



  • @yoonzm
    你这么写思路是对的,其实在Redux的reducer中或者useReducer中都是用type和payload来表示到底对应用的那个部分进行变化,怎么变化.
    但是你这么写又是违反React+Redux基本原则的. 数据的获取是在Redux中进行,然后以props的形式传递给应用的组件. 你需要思考怎么才算是单向的数据流动.
    在Redux的标准应用中,数据和State的操作都是在Redux中完成的. 标准-单向流动,



  • @phpsmarter214
    恩,理解了,例子中所写的请求是可以在action中完成的,我这样写确实感觉不符合redux的规范,不太优雅。