在接下来的两篇文章中,我想谈谈在 React 应用中使用 Redux-Saga 进行异步 action 管理的基础和进阶方法。我会说明为何咱们会在 AppsFlyer 项目中使用它,以及它能够解决什么问题。前端
本篇文章主要介绍 Redux-Saga 相关的基本概念,下篇专门讨论 Redux-Saga 能够解决哪些问题。请注意:阅读这两篇文章,你要对 React 和 Redux 有必定的了解。react
为了理解 Sagas,咱们首先要理解什么是 Generator。下面是 MDN 对 Generator 的描述:android
Generator 是在执行时能暂停,后面又能从暂停处继续执行的函数。它的上下文会在继续执行时保存。ios
你能够把 Generator 理解成一种遍历器对象生成函数,(译注:Generator 执行后返回的遍历器对象)提供一个 next
方法。执行这个方法就会返回下一个状态,或者返回遍历结束的状态。这就须要 Generator 可以维护内部状态。git
下面是一个基本的 Generator 示例,它生成的遍历器对象会返回几个字符串:github
function* namesEmitter() { yield "William"; yield "Jacob"; return "Daniel"; } // 执行 Generator var generator = namesEmitter(); console.log(generator.next()); // prints {value: "William", done: false} console.log(generator.next()); // prints {value: "Jacob", done: false} console.log(generator.next()); // prints {value: "Daniel", done: true} 复制代码
next
方法的返回值结构很是简单 — 只要咱们经过 yield/return
返回值,这个返回值就是 value
属性的值。若是咱们没有返回值,value
属性的值就是 undefined,done
属性的值就是 true
。 还有一点值的注意的是,执行 namesEmitter
后,函数会在调用 yield
的地方停下来。当咱们调用 next
方法后,函数会继续执行,直到遇到下一个 yield
。若是咱们调用了 return
语句或者函数执行完毕,done
属性就会为真。redux
若是状态序列的长度不肯定时,咱们能够用下面的方法来写:后端
var results = generator.next(); while(!results.done){ console.log(results.value); results = generator.next(); } console.log(results.value); 复制代码
Sagas 是经过 Generator 函数来建立的。官方文档 的解释以下:api
Saga 就像应用中的一个独立线程,彻底负责管理异步 action。bash
你能够把 Saga 想象成一个以最快速度不断地调用 next
方法并尝试获取全部 yield
表达式值的线程。你可能会问这和 React 有什么关系,为何要使用它,因此首先来看看如何在 React & Redux 应用使用 Saga:
在 React & Redux 应用中,一个常见的用法从调用一个 action 开始。被分配用来处理这个 action 的 reducer 会使用新的 state 更新 store,随后视图就会被更新渲染。 若是一个 Saga 被分配用来处理这个 action — 这个 action 一般就是个异步 action(好比一个对服务端的请求),一旦这个 action 完成后,Saga 会调用另外一个 action 让 reducer 进行处理。
咱们能够经过一个常见流程来讲明: 用户与页面进行交互,这个交互动做会触发一个从服务端请求数据的动做(此时页面显示 loading 提示),最终咱们用请求回来的数据去渲染页面的内容。 让咱们为每步建立一个 action,而后用 Redux-Saga 实现一个简化的版本以下:
// saga.js import { take } from 'redux-saga/effects' function* mySaga(){ yield take(USER_INTERACTED_WITH_UI_ACTION); } 复制代码
这个 Saga 的函数名叫作 mySaga
。它调用了 Redux-Saga effect 的 take
方法,这个方法会阻塞 Saga 的执行,直到有人调用了做为参数的那个 action,Saga 的执行也会结束,就像咱们前面看到的 Generator 同样(done 变为 true)。
如今咱们要让页面展现 loading 提示来响应这个 action。能够经过 put
方法调用另外一个 action,而后分配 reducer 来处理,从而完成上述功能。以下:
// saga.js import { take, put } from 'redux-saga/effects' function* mySaga(){ yield take(USER_INTERACTED_WITH_UI_ACTION); yield put(SHOW_LOADING_ACTION, {isLoading: true}); } // reducer.js ... case SHOW_LOADING_ACTION: (state, isLoading) => { return Object.assign({}, state, {showLoading: isLoading}); } ... 复制代码
下一步是调用 call
方法,它接收一个函数和一组参数,使用这些参数来执行这个函数。咱们给 call
方法传递一个请求服务端并返回一个 Promise 的 GET
函数,它会保存请求结果:
// saga.js import { take, put, call } from 'redux-saga/effects' function* mySaga(){ yield take(USER_INTERACTED_WITH_UI_ACTION); yield put(SHOW_LOADING_ACTION, {isLoading: true}); const data = yield call(GET, 'https://my.server.com/getdata'); yield put(SHOW_DATA_ACTION, {data: data}); } // reducer.js ... case SHOW_DATA_ACTION: (state, data) => { return Object.assign({}, state, {data: data, showLoading: false}; } ... 复制代码
经过调用 SHOW_DATA_ACTION 来用接收的数据更新页面。
应用启动后,全部的 Sagas 都会被执行,你能够认为一直在调用 next
方法直到结束。take
方法相似于线程挂起的做用,一旦调用了USER_INTERACTED_WITH_UI_ACTION,线程就会恢复执行。
而后,咱们继续调用 SHOW_LOADING_ACTION,reducer 会处理这个 action。因为 Saga 还在继续运行,call
方法会发起对服务端的请求,Saga 会在再次挂起,直到请求结束。
在上面的例子中,Saga 只处理了一个用户交互的 action,由于咱们用 put
方法执行了 SHOW_DATA_ACTION
这个 action,而后后面就没有 yield 了(done 就是 true 了对吧?)。
若是咱们但愿在每次调用 USER_INTERACTED_WITH_UI_ACTION
这个 action 的时候,都会执行这一系列的 actions,咱们能够用 while(true)
语句来包裹 Saga 内部的逻辑代码。完整代码以下:
// saga.js import { take, put, call } from 'redux-saga/effects' 1. function* mySaga(){ 2. while (true){ 3. yield take(USER_INTERACTED_WITH_UI_ACTION); 4. yield put(SHOW_LOADING_ACTION, {isLoading: true}); 5. const data = yield call(GET, 'https://my.server.com/getdata'); 6. yield put(SHOW_DATA_ACTION, {data: data}); 7. } 8. } // reducer.js ... case SHOW_LOADING_ACTION: (state, isLoading) => { return Object.assign({}, state, {showLoading: isLoading}); }, case SHOW_DATA_ACTION: (state, data) => { return Object.assign({}, state, {data: data, showLoading: false}; } ... 复制代码
这个无限循环不会形成堆栈溢出,也不会使你的应用崩溃!由于 take
方法就像线程挂起同样,mySaga
执行后会一直保持 pending
状态,直到那个 action 被触发。下次从新进入循环后,也会重复上述过程。
让咱们一步步地看一下上面的过程:
while(true)
循环,在第 3 行挂起。USER_INTERACTED_WITH_UI_ACTION
这个 action 被触发。SHOW_LOADING_ACTION
这个 action,而后分配的 reducer 进行处理(reducer 处理后,页面就会显示 loading 提示)。SHOW_DATA_ACTION
接收 data 做为参数被触发,而后 reducer 就可使用这些数据来更新页面。在这篇文章中,咱们介绍了 Redux-Saga 相关的基本概念,展现了如何在 React 应用中使用它。下篇文章中,我会展现在实际应用中使用它得到的价值。
感谢 Yotam Kadishay 和 Liron Cohen。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。