依照惯例,开头先放出redux中文文档地址
异步场景的基本思路
之前讨论的所有场景都是同步场景,实际开发中肯定有很多异步场景,下面讨论一下异步场景的问题。
首先,异步场景和同步场景本质上还是利用action
、reducer
、store
这些东西,只是设计reducer
和action
的时候更加巧妙,考虑到了异步状态。
对应不同的action
,在reducer
中添加专门表现异步状态的内容。
function post(
state = {
isFetching: false,
items: []
},
action
) {
switch(action.type) {
case REQUEST:
return Object.assign({}, state, {
isFetching: true,
})
case RECEIVE:
return Object.assign({}, state, {
isFetching: false,
items: action.posts
})
default:
return state
}
}
搞定了reducer
还要考虑异步场景的action
,我们先不仔细探究代码实现,只要想明白如果store.dispatch(someAction)
,然后按照异步回调的顺序触发上面的reducer
,那么异步场景就实现了。
解决异步场景
上面我们笼统地讲了异步场景的实现思路,目前看来reducer
部分已经没有大问题和同步场景十分类似,action
部分和同步场景有所区别。
简单思考可以发现,我们需要在action creator
函数中实现异步,即当我们调用一个action creator
时,不是简单需要返回一个action
对象,而是根据异步的场景返回不同的action
。看起来我们需要改造action
。
理解了这个问题,就找到了异步场景的症结。
redux
有许多配套的中间件(middleware
),关于中间件的概念可以查看上面的文档,我们不仔细讨论。简要地说,中间件在调用action creator
后起作用,可以有多个中间件存在,数据流会在中间件之间传递,数据流经过所有中间件处理流出时应该还是一个简单的action
对象,到达store.dispatch(action)
时所有内容应该和同步数据流一致。
redux-thunk 实现
redux-thunk
改造了store.dispatch
,使action creator
能接收一个函数作为参数。改造完action creator
之后,就可以创造一个内部根据状态再次异步操作的action
。
const fetchPosts = postTitle => (dispatch, getState) => {
dispatch(requestPosts(postTitle));
return fetch(`/some/API/${postTitle}.json`)
.then(response => response.json())
.then(json => dispatch(receivePosts(postTitle, json)));
};
};
结合一个小例子来看一下
已有一个container
组件
class example extends Component {
constructor() {
super();
}
componentDidMount() {
组件需要调用后端API载入数据
this.props.getName();
}
render () {
console.log(this.props)
return (
<div>
// 组件按照不同状态显示‘querying...’/正常数据
{ this.props.info.server.isFetching ?
(
<span>
querying...
</span>
)
:
(
// 点击请求API刷新状态
<span onClick={() => this.props.getName()}>
{ this.props.info.server.name }
</span>
)
}
...
</div>
);
}
}
const mapStateToProps = (state) => {
return {
info: state.info
}
};
const mapDispatchToProps = (dispatch) => {
return {
getName: () => {
dispatch(getServerInfo());
}
};
};
const Example = connect(
mapStateToProps,
mapDispatchToProps
)(example);
export default Example;
下面开始实现这个小功能
编写对应的action
function fetchServerInfo() {
return {
type: 'GET SERVER INFO'
};
}
function receiveServerInfo({ payload: server }) {
return {
type: 'RECEIVE SERVER INFO',
payload: server
};
}
// 这是一个改造后的action creator,可以异步dispatch(action)
function getServerInfo() {
return (dispatch, getState) => {
dispatch(fetchServerInfo());
return fetch(`http://server/info`)
.then(
response => response.json(),
error => console.log('an error', error)
)
.then(
(json) => {
let { server } = json;
dispatch(receiveServerInfo({ payload: server }));
}
)
}
}
编写reducer
// 加入一个isFetching的状态标志位
const initialInfo = {
server: {
isFetching: false,
name: 'localhost',
status: ''
}
};
function setServerName (state = initialInfo, action) {
let server = state.server;
switch (action.type) {
case 'GET SERVER INFO':
return Object.assign(
{},
server,
{
isFetching: true
}
);
case 'RECEIVE SERVER INFO':
let { payload: { name } } = action;
return Object.assign(
{},
server,
{
isFetching: false,
name
}
);
default:
return server
}
}
function info (state = initialInfo, action) {
return {
server: setServerName(state.title, action)
}
}
export default info;
添加middleware
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleWare from 'redux-thunk';
...
const store = createStore(
reducer,
applyMiddleware(
thunkMiddleWare
)
);
redux-saga 实现
redux-saga
是一种不一样的实现方式,它没有在action creator
上做文章,它监听了action
,如果action
中有effect
,那么它可以调用Genertor
函数实现异步。
还是结合上面的小例子来看一下:
已有一个container
组件
class example extends Component {
constructor() {
super();
}
componentDidMount() {
组件需要调用后端API载入数据
this.props.getName();
}
render () {
console.log(this.props)
return (
<div>
// 组件按照不同状态显示‘querying...’/正常数据
{ this.props.info.server.isFetching ?
(
<span>
querying...
</span>
)
:
(
// 点击请求API刷新状态
<span onClick={() => this.props.getName()}>
{ this.props.info.server.name }
</span>
)
}
...
</div>
);
}
}
const mapStateToProps = (state) => {
return {
info: state.info
}
};
const mapDispatchToProps = (dispatch) => {
return {
getName: () => {
dispatch(getServerInfo());
}
};
};
const Example = connect(
mapStateToProps,
mapDispatchToProps
)(example);
export default Example;
创建一个saga
文件监听action
import { takeEvery } from 'redux-saga';
import { call, put } from 'redux-saga/effects';
import requestServerInfo from '../../services/info'
// 遇到被监听的action会调用下面的函数,并将异步操作分解
function* getServerInfo() {
try {
yield put({ type: 'GET SERVER INFO' });
const { data: { server: server } } = yield call(requestServerInfo, 'http://server/test');
yield put({ type: 'RECEIVE SERVER INFO', payload: { server } });
} catch (e) {
yield put({ type: 'RECEIVE AN ERROR' });
}
}
// 在此处使用takeEvery监听action
function* infoSaga() {
yield* takeEvery('QUERY_INFO', getServerInfo);
}
export { infoSaga };
在store
中应用saga
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { infoSaga } from '../sagas/info';
import reducer from '../reducers/all';
// 引入redux-saga中间件
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
applyMiddleware(
sagaMiddleware
)
);
sagaMiddleware.run(infoSaga);
export default store;
剩下reducer
部分和上面一样处理加入isFetching
标志位即可,没有变化
网友评论