在 model 中请求服务端数据

******如果说 model,namespace,connect,dispatch,action,reducer 这些是 dva 的基石,那 dva 的精髓就体现在于 effect。*******

关于EffectGenerator function

那么 effect 到底是什么呢 ?effect 是一个 dva 语境中的名词。和 reducers 类似,我们也可以在 dva model 中定义一个 effects 成员。

export default {
  namespace: 'some_namespace',
  state: {},
  effects: { // 定义 effects 成员
    'someEffect': function*() {},
    'someOtherEffect': function*() {},
    // ...
  },
  reducers: {
    // ...
  },
}

局部上看 effect 就是一个一个
生成器函数generator function。宏观上看,effect 是一层中间件。

在上一章中 action 被 dispatch 之后就能够 直接 到达 reducer。为了保证 reducer 的纯粹性,但同时又能够处理副作用,就需要打破「直接」这个特性。effect 充当了这么一个中间层,当 action 被 dispatch 之后,会先到达 effect 处理副作用,然后该 effect 最终会促使新的 action 发送出去,这个新的 action 可能被其他的 effect 再捕获继续处理,也可能被 reducer 捕获并结束,无论怎样,最终处理逻辑的终点都将是 reducer。

在上一章节中,我们知道 action.type 的构造是 namespace 名称 + / + reducer 名称,事实上 action.type 也可以是 namespace 名称 + / + effect 名称。对于视图层来讲,其实并不会感知 effect 和 reducer 的区别。视图层只是通过 action 描述想做什么,至于这个 action 之后是直接被 reducer 处理还是通过 effect 再到 reducer,视图层并不感知,也不应该关心。这样我们就做到了数据逻辑和视图逻辑的分离处理。

Generator function

我们再解释为什么 generator function 可以用来处理异步逻辑。其实 generator function 处理异步逻辑并不是 dva 的专利,在许多 js 框架中都用到了,最著名的就是 co。使用 generator function 处理异步也不是对语言特性的乱用,而是说 generator function 天然地就具备处理异步的特质。dva 中一个典型的 effect 的写法是:

getData: function* ({ payload }, { call, put }) {
  const data = yield call(SomeService.getEndpointData, payload, 'maybeSomeOtherParams');
  yield put({ type: 'getData_success', payload: data });
}

先说结论:当这个 generator function 被执行时,执行的流程 看上去 会是同步的!入参有两个对象,第一个对象就是匹配这个 effect 的 action 对象,因此可以取到约定的 payload 这个字段,第二个对象是 effect 原语集,
其中 call, put 最为常用。generator function 入参中的两个对象都是在运行时由 dva 注入到 generator function 中的。call 其实是一个函数,和 yield 关键字配合使用处理异步逻辑call 第一个参数是一个函数,要求函数返回 Promise,
之后的参数是该函数调用时的入参。yield call 调用后就阻塞了,Promise 被解析后,得到异步调用的结果,存储到 data 中,然后程序才能继续进行。看到下面一行又执行了 put。
put 也是一个函数,put 和 yield 配合使用,用来派发一个 action,和 dispatch 的功能 一模一样
只不过是在 effect 函数中使用而已。

注意:yield put 派发的 action 如果是为了触发 同 model 中的其他 effect/reducer 执行,不需要指定 namespace 名称。

 异步的实质是事件发生促使程序的执行点来回跳转。我们使用 callback 本质上是描述跳转的一种手段。generator function 并没有改变异步的本质,只是改变了描述的方式,使得程序看起来像是同步一样。

一个 generator function 在执行时有 两方。一方是 generator function 本身,另一方是 generator function 的句柄持有者,而这一般都是框架所持有。我们姑且称这个句柄为 genStub。当框架调用 genStub.next() 时,generator function 会执行到下一个 yield 然后暂停,并把 yield 后面表达式的计算值返还给框架,同时把程序执行权交给框架。框架拿到值后做处理,比如就是异步处理,处理结束拿到结果,再次调用 genStub.next(),返还值给 generator function 同时驱动它恢复执行。当恢复执行时,你可以认为 返回的处理结果会整体替换 yield <expression>,然后程序继续执行到下一个 yield。

 

yield 这个单词用在这里特别形象:yield 本身有「让步」的意思,也有「产出」的意思。

 

「generator function yield 到外部的值」和「外部返还给 generator function 的值」不是一回事!!!

 

generator function 定义了流程,并在每次 yield 节点上报想做的事情。而异步的真正执行逻辑由 generator function 句柄的持有者代为执行。对应到 dva 上也是一样的。拿 call 做例子,call 其实是一个特别简单的函数。call 的返回值只是一个 plain javascript object:

 

{
  CALL: {
    fn: SomeService.getEndpointData,
    args: [payload, 'maybeSomeOtherParams']
  }
}

 

我们通过 call 向 dva 描述了想做的事情:请帮我执行这个函数,Promise 解析后通知我继续执行,并把 Promise 的解析值返回给我。

 











posted @ 2019-07-24 19:55  DeeplyWeb  阅读(348)  评论(0编辑  收藏  举报