了解了redux,还需要好好了解 Redux 相关的生态。
一、redux-thunk
redux-thunk是什么?redux-thunk是redux的中间件, 利用 redux 的中间件可以让 redux 支持异步的 action。
如下:原始的Redux
里面,action creator
必须返回plain object
,而且必须是同步的。
function increment() {
return {
type: 'INCREMENT'
}
};
store.dispatch(increment());
如下:涉及到异步操作
function increment() {
return {
type: 'INCREMENT'
}
};
// 异步action creator
function incrementAsync() {
return (dispatch) => {
setTimeout(() => {
dispatch(increment());
}, 1000);
}
}
// 使用了Redux-Thunk后dispatch不仅仅可以发出plain object,还可以发出这个异步的函数
store.dispatch(incrementAsync());
综上可以看出:在使用Redux-Thunk
前我们dispatch
的action
必须是一个纯对象(plain object
),使用了Redux-Thunk
后,dispatch
可以支持函数,这个函数会传入dispatch
本身作为参数。
没有 redux-thunk
如果没有 redux-thunk ,异步 action有两种办法如下:
1、先创建「请求数据」的 action,然后再创建一个「更新数据」的 action:
2、直接发请求,得到数据之后创建「更新数据」的 action
setTimeout(() => {
store.dispatch(increment());
}, 1000);
如下实现一个简单的通知,5秒后关闭:
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
这样写同样可以在1秒后发出增加的action
,而且代码还更简单,那我们为什么还要用Redux-Thunk
呢,他存在的意义是什么呢?stackoverflow对这个问题有一个很好的回答,而且是官方推荐的解释。
翻译就是下面的意思:
**不要觉得一个库就应该规定了所有事情!**如果你想用JS处理一个延时任务,直接用
setTimeout
就好了,即使你使用了Redux
也没啥区别。Redux
确实提供了另一种处理异步任务的机制,但是你应该用它来解决你很多重复代码的问题。如果你没有太多重复代码,使用语言原生方案其实是最简单的方案。
具体说明可以看StackOverflow,也可以看翻译后的https://juejin.cn/post/6869950884231675912
redux中间件大概就长这个样子:
- 一个中间件接收
store
作为参数,会返回一个函数 - 返回的这个函数接收老的
dispatch
函数作为参数(也就是上面的next
),会返回一个新的函数 - 返回的新函数就是新的
dispatch
函数,这个函数里面可以拿到外面两层传进来的store
和老dispatch
函数
function logger(store) {
return function(next) {
return function(action) {
console.group(action.type);
console.info('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
console.groupEnd();
return result
}
}
}
redux-thunk 的源码如下:https://github.com/reduxjs/redux-thunk/blob/master/src/index.js
如上,一共就14行代码,在简化以下就如下:
const thunk = ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
export default thunk
//详细如下:
function thunk(store) {
return function (next) {
return function (action) {
// 从store中解构出dispatch, getState
const { dispatch, getState } = store;
// 如果action是函数,将它拿出来运行,参数就是dispatch和getState
if (typeof action === 'function') {
return action(dispatch, getState);
}
// 否则按照普通action处理
let result = next(action);
return result
}
}
}
这段代码的逻辑就是:
- 如果 action 是个函数,就调用这个函数
- 如果 action 不是函数,就传给下一个中间件
其实就是:发现 action 是函数就调用它
有了 redux-thunk:action 就可以是函数了
const action = function(dispatch) {
return fetchUsers().then(
(users) => dispatch({type:'updateUsers', payload: users}),
(error) => dispatch({type:'updateUsersError'}),
);
};
dispatch(action)
这个 action 会被调用,然后在将来某个时候创建 updateUsers action,所以我们可以称它为异步 action(其实就是函数),redux-thunk 就是帮你调用这个函数。
源码中:在thunk函数外又包裹了一层,其实就是传递额外的参数
const api = "http://www.example.com/sandwiches/";
const whatever = 42;
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument({ api, whatever })),
);
二、redux-saga
对于复杂场景,Redux-Thunk
并不适用,推荐Redux-Saga
来处理复杂副作用。Redux-Saga也是
在实际工作中使用最多的Redux
异步解决方案。Redux-Saga
比Redux-Thunk
复杂得多,而且他整个异步流程都使用Generator
来处理,Generator
也是我们这篇文章的前置知识,如果你对Generator
还不熟悉,可以看看这篇文章。
redux-saga 是一个 redux 中间件,它具有如下特性:
-
集中处理 redux 副作用问题。
-
被实现为 generator 。
-
类 redux-thunk 中间件。
-
watch/worker(监听->执行) 的工作形式。
简单案例:点击一个按钮去请求用户的信息,大概长这样
import React from 'react';
import { connect } from 'react-redux';
function App(props) {
const { dispatch, userInfo } = props;
const getUserInfo = () => {
dispatch({ type: 'FETCH_USER_INFO' })
}
return (
<div className="App">
<button onClick={getUserInfo}>Get User Info</button>
<br></br>
{userInfo && JSON.stringify(userInfo)}
</div>
);
}
const matStateToProps = (state) => ({
userInfo: state.userInfo
})
export default connect(matStateToProps)(App);
store.js:
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducer';
import rootSaga from './saga';
const sagaMiddleware = createSagaMiddleware()
let store = createStore(reducer, applyMiddleware(sagaMiddleware));
// 注意这里,sagaMiddleware作为中间件放入Redux后
// 还需要手动启动他来运行rootSaga
sagaMiddleware.run(rootSaga);
export default store;
saga.js:
import { call, put, takeLatest } from 'redux-saga/effects';
import { fetchUserInfoAPI } from './api';
function* fetchUserInfo() {
try {
const user = yield call(fetchUserInfoAPI);
yield put({ type: "FETCH_USER_SUCCEEDED", payload: user });
} catch (e) {
yield put({ type: "FETCH_USER_FAILED", payload: e.message });
}
}
function* rootSaga() {
yield takeEvery("FETCH_USER_INFO", fetchUserInfo);
}
export default rootSaga;
takeEvery
:他的作用是监听每个FETCH_USER_INFO
,当FETCH_USER_INFO
出现的时候,就调用fetchUserInfo
函数,注意这里是每个FETCH_USER_INFO
。也就是说如果同时发出多个FETCH_USER_INFO
,我们每个都会响应并发起请求。takeLatest
:takeLatest
从名字都可以看出来,是响应最后一个请求,具体使用哪一个,要看具体的需求。