欢迎来到好多视频第237期,这次咱们来《用 redux-saga 实现异步请求》。
本期代码: https://github.com/happycasts/episode-237-demo
首先要有一个基本能用的 redux 环境,可以直接下载我的 react-starter-v1.0.0
下载解压之后,重命名一下
mv react-starter episode-237-demo
这个环境是基于 create-react-app 的。
先装包,把项目跑起来
cd episode-237-demo
npm i
npm start
到浏览器中,访问 localhost:3000 ,点一下页面上的按钮,是可以加载出一些文章的标题的。
不过这个效果是用 redux-thunk 实现的,这里咱们改用 saga 来做。
卸载 thunk 的 npm 包,安装 redux-saga
npm uninstall redux-thunk
npm i redux-saga
然后代码上调整一下。
diff --git a/src/actions/index.js b/src/actions/index.js
@@ -2,13 +2,10 @@ import axios from 'axios'
import { POSTS_URL } from '../constants/ApiConstants'
import * as types from '../constants/ActionTypes'
-export const loadPosts = () => dispatch => {
- axios.get(POSTS_URL).then(
- res => {
- dispatch({
- type: types.LOAD_POSTS,
- posts: res.data
- })
- }
- )
+export const fetchPostsRequest = () => ({
+ type: types.FETCH_POSTS_REQUEST
+})
+
+export const fetchPosts = () => {
+ console.log('fetchPosts...执行异步操作')
}
diff --git a/src/components/Posts.js b/src/components/Posts.js
@@ -3,10 +3,10 @@ import styled from 'styled-components'
class Posts extends Component {
render () {
- const { loadPosts, posts } = this.props
+ const { fetchPostsRequest, posts } = this.props
return (
<Wrap>
- <Button onClick={loadPosts}>
+ <Button onClick={fetchPostsRequest}>
加载文章
</Button>
{
diff --git a/src/constants/ActionTypes.js b/src/constants/ActionTypes.js
@@ -1 +1,2 @@
export const LOAD_POSTS = 'LOAD_POSTS'
+export const FETCH_POSTS_REQUEST = 'FETCH_POSTS_REQUEST'
diff --git a/src/containers/PostsContainer.js b/src/containers/PostsContainer.js
@@ -1,7 +1,7 @@
import React from 'react'
import Posts from '../components/Posts'
import { connect } from 'react-redux'
-import { loadPosts } from '../actions'
+import { fetchPostsRequest } from '../actions'
const PostsContainer = props => <Posts {...props} />
@@ -10,5 +10,5 @@ const mapStateToProps = state => ({
})
export default connect(mapStateToProps, {
- loadPosts
+ fetchPostsRequest
})(PostsContainer)
diff --git a/src/store/index.js b/src/store/index.js
@@ -1,8 +1,12 @@
import { createStore, applyMiddleware } from 'redux'
import rootReducer from '../reducers'
import logger from 'redux-logger'
-import thunk from 'redux-thunk'
+import createSagaMiddleware from 'redux-saga'
+import mySaga from './sagas'
-let middlewares = [logger, thunk]
+const sagaMiddleware = createSagaMiddleware()
+let middlewares = [logger, sagaMiddleware]
export default createStore(rootReducer, applyMiddleware(...middlewares))
+
+sagaMiddleware.run(mySaga)
diff --git a/src/store/sagas.js b/src/store/sagas.js
@@ -0,0 +1,10 @@
+import { takeLatest } from 'redux-saga/effects'
+import * as types from '../constants/ActionTypes'
+
+import { fetchPosts } from '../actions'
+
+function * mySaga () {
+ yield takeLatest(types.FETCH_POSTS_REQUEST, fetchPosts)
+}
+
+export default mySaga
创建 store/sagas.js 文件,先从 redux-saga/effects 中导入 takeLastest ,然后导入常量文件中的 action 类型定义。从 action 创建器文件中导入 fetchPosts 函数。创建一个新的 generator 函数叫做 mySaga ,然后 yield takeLatest 传入两个参数,分别是 action 类型,和 fetchPosts 函数。导出 mySaga 。
takeLastest 的作用就是等待监听 store 中的 action ,一旦 FETCH_POSTS_REQUEST
这个 action 发出后,fetchPosts 才会开始执行。takeLastest 中的 Latest 表示最新的,如果 fetchPosts 在执行过程中,takeLastest 再次接收到了 FETCH_POSTS_REQUEST
,那么它就会取消正在进行的操作,而去响应最近的这一次 ,这就是 takeLastest 这个名字的由来,中文直接翻译过来就是“使用最近的一次”。
下面要连接 sagas 文件的内容到 redux store。
先到 store/index.js 中。导入 createSagaMiddleware ,然后导入 mySaga . 下面初始化 sagaMiddleware ,并加载到 store 中。最后运行 sagaMiddleware.run 来执行 mySaga 。
剩下的几个文件的修改都是要触发 FETCH_POSTS_REQUEST
这个 action 的代码。
Action 创建器文件 actions/index.js 中,把原来 thunk 思路的代码 loadPosts 函数删除。定义 fetchPostsRequest ,发出触发 saga 代码的 action 。mySaga 被执行后,会触发 fetchPosts 函数,其中会执行异步操作,但是暂时先打印一些信息,证实一下它是否能被正确触发。
PostsContainer 文件中,删除 loadPosts ,导入 fetchPostsRequest 。
Posts.js 中,点按钮的时候触发 fetchPostsRequest 。
ActionTypes 里面定义了 action 类型常量 FETCH_POSTS_REQUEST
到浏览器中,点一下“加载文章”按钮,会看到 FETCH_POSTS_REQUEST
action 会被发出,然后触发 mySaga 中的 takeLastest 代码,最终执行了 fetchPosts 函数,打印出了信息。
下一步来看如何执行异步操作。
git a/src/actions/index.js b/src/actions/index.js
@@ -1,11 +1,15 @@
import axios from 'axios'
import { POSTS_URL } from '../constants/ApiConstants'
import * as types from '../constants/ActionTypes'
+import { call } from 'redux-saga/effects'
+
+const api = url => axios.get(url).then(res => res.data)
export const fetchPostsRequest = () => ({
type: types.FETCH_POSTS_REQUEST
})
-export const fetchPosts = () => {
- console.log('fetchPosts...执行异步操作')
+export function * fetchPosts () {
+ const posts = yield call(api, POSTS_URL)
+ console.log(posts)
}
这次从 redux-saga/effects 中导入 call 接口,下面的 fetchPosts 要改写成一个 generator ,yield 会把 call 语句交给 redux-saga 去执行,执行期间 fetchPosts 中的语句会暂停执行。call 的一个参数 api 是执行异步请求的接口函数,暂时还没有定义,POSTS_URL 是咱们的 starter 代码中本来就定义好的一个常量,是一个公用 API 的链接,可以请求得到所有文章数据。数据返回后 call 语句执行结束,拿到的网络数据赋值给 posts ,然后才会继续执行下一句 console.log 打印出 posts 的值,这里就充分体现出来 generator 的优势了,虽然咱们这里有异步请求,但是语句写的还是跟同步时候一样。
最后来定义 api 接口,用 axios 发 get 请求给 POSTS_URL
对应的链接,最终返回请求到的数据,也就是所有博客的数据。
到浏览器中,点一下按钮,可以看到,几秒之后,posts 的数据就被打印出来了。
数据到手,下一步就是发出 action 来修改 store 了,redux-saga 下就不用 dispatch 发 action 了,而是用 put 。
diff --git a/src/actions/index.js b/src/actions/index.js
@@ -1,7 +1,7 @@
import axios from 'axios'
import { POSTS_URL } from '../constants/ApiConstants'
import * as types from '../constants/ActionTypes'
-import { call } from 'redux-saga/effects'
+import { call, put } from 'redux-saga/effects'
const api = url => axios.get(url).then(res => res.data)
@@ -11,5 +11,5 @@ export const fetchPostsRequest = () => ({
export function * fetchPosts () {
const posts = yield call(api, POSTS_URL)
- console.log(posts)
+ yield put({ type: types.FETCH_POSTS_SUCCESS, posts })
}
diff --git a/src/constants/ActionTypes.js b/src/constants/ActionTypes.js
@@ -1,2 +1,2 @@
-export const LOAD_POSTS = 'LOAD_POSTS'
+export const FETCH_POSTS_SUCCESS = 'FETCH_POSTS_SUCCESS'
export const FETCH_POSTS_REQUEST = 'FETCH_POSTS_REQUEST'
diff --git a/src/reducers/post.js b/src/reducers/post.js
index 04e2b01..1322292 100755
--- a/src/reducers/post.js
+++ b/src/reducers/post.js
@@ -3,7 +3,7 @@ import * as types from '../constants/ActionTypes'
const all = (state = [], action) => {
switch (action.type) {
- case types.LOAD_POSTS:
+ case types.FETCH_POSTS_SUCCESS:
return action.posts
default:
return state
到 actions/index.js 中,从 redux-saga/effects 中再导出 put 接口,generator 函数中把 put 语句 yield 也就是上交给 redux-saga 执行,saga 会吧 put 参数中的 action ,dispatch 出来。发出的 action 类型是 FETCH_POSTS_SUCCESS
,负载数据是包含所有文章信息的数据。
到 ActionTypes.js 中,原来的 LOAD_POSTS
就不要了,改为 FETCH_POSTS_SUCCESS
。
到 reudcers/posts.js 中,改一下 action 类型名即可。
浏览器中,点一下按钮,稍后会看到文章已经成功加载了。
那咱们这期《用 redux-saga 实现异步请求》的任务也就完成了。