Take about Redux I
本来我们组用的前端技术一般 也就是利用基于react
的AntD
脚手架
后来组里来了一个pku的dalao 科普了 react
全家桶
目前我们项目里除了基础的redux
之外,还用了saga
中间件 react-router
在了解redux
之前 可能你不一定要用redux
经常有人会说 不会redux
说明你不需要用redux
“You Might Not Need Redux”
————————————————————————
什么是Redux?
讲起来redux
第一次代码提交也就在三年前 一晃作为react
家族一员 被广泛应用于各web构建
先来看一段官方文档的介绍
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
通俗的来讲redux
做的工作类似于state
做的工作,它是来管理数据状态的,解决数据变化和异步这两个问题的工具。
将原来事件驱动的服务,变化为数据驱动的服务。
redux有什么特点?
redux
一般是由
- action
- reducer
- store
- MiddleWare
相对于state
,首先redux
里面的数据是可以跨页面的,其次redux
的数据是只读的,如果需要更改数据必须调用相应的action
。
当然这点也很好理解,因为redux
读取时用的是this.props props
本身就是只读的。(从这个角度 也可以把redux
理解为父组件统一管理状态的库)
reducer
函数是一个纯函数,只根据对应的action
和之前的state
,返回新的state
。
Action
顾名思义 是一个存放行为的函数。
组件通过dispath
调用action
函数,action
函数收到request
把对应的type
和 相应的参数 传递给 store
import types from '../constants/actionTypes';
import { createActions } from '../util/utils';
//同步action
export function fetchCollapseChange(collapse) {
return {
type: types.CHANGE_COLLAPSE,
payload: {
collapse,
},
};
}
//异步action
export const fetchTimeInfo = createActions([
types.FETCH_TIME_INFO_REQUEST,
types.FETCH_TIME_INFO_SUCCESS,
types.FETCH_TIME_INFO_FAILURE,
]);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createConstants } from '../util/utils';
export default createConstants(
'CHANGE_COLLAPSE',
'FETCH_TIME_INFO_REQUEST',
'FETCH_TIME_INFO_SUCCESS',
'FETCH_TIME_INFO_FAILURE',
}
2
3
4
5
6
7
8
9
10
const LoadStatus = {
CACHED: 'cached',
CHECKING: 'checking',
LOADING: 'loading',
UPDATING: 'updating',
PROCESSING: 'processing',
FINISHED: 'finished',
ERROR: 'error',
NONE: 'none',
};
export default LoadStatus;
2
3
4
5
6
7
8
9
10
11
12
Reducer
当Action
执行之后 得到了对应的Action_type
。根据这些type
,reducer
函数更新相应的数据,注意reducer
是一个纯函数,不执行复杂的数据处理过程。
Reducer
分为初始化,action
更新两部分。
import types from '../constants/actionTypes';
import LoadStatus from '../constants/loadStatus';
function getInitState() {
return {
loginLayout: {
collapse: false,
time: '0000-00-00 00:00',
timeNum: 0,
loadStatus: LoadStatus.NONE,
},
};
}
function layoutReducer(state = getInitState(), action) {
const { payload } = action;
switch (action.type) {
case types.CHANGE_COLLAPSE:
return {
...state,
loginLayout: {
...state.loginLayout,
collapse: payload.collapse,
},
};
case types.FETCH_TIME_INFO_REQUEST:
return {
...state,
loginLayout: {
...state.loginLayout,
loadStatus: LoadStatus.LOADING,
},
};
case types.FETCH_TIME_INFO_SUCCESS:
return {
...state,
loginLayout: {
...state.loginLayout,
loadStatus: LoadStatus.FINISHED,
time: payload.time,
timeNum: payload.result,
},
};
case types.FETCH_TIME_INFO_FAILURE:
return {
...state,
loginLayout: {
...state.loginLayout,
loadStatus: LoadStatus.ERROR,
},
};
default:
return state;
}
}
export default layoutReducer;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import layoutReducer from './layoutReducer';
const rootReducer = combineReducers({
layout: layoutReducer,
});
export default rootReducer;
2
3
4
5
6
7
8
9
Storestore
就相当于一个存储库,受到各种函数调用,当store
里面的参数值更新之后 把更新后的参数返回给各个组件。
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { fetchUserInfo } from './actions/userAction';
import { fetchCollapseChange } from './actions/layoutAction';
//把所有state值 存入props中
function mapStateToProps(state) {
const {
user: {
loginUser: {
isAdmin,
},
},
layout: {
loginLayout: {
collapse,
},
},
} = state;
return {
isAdmin,
collapse,
};
}
//把所有dispatch 函数 存入props
function mapDispatchToProps(dispatch) {
return {
fetchUserInfo() {
dispatch(fetchUserInfo.request());
},
fetchCollapseChange(collapse) {
dispatch(fetchCollapseChange(collapse));
},
};
}
//withRouter
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps,
)(AppLayout));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
当props
更新之后 render
会重新渲染一次 以实现类似setState
的功能。
此外redux
可以通过componentWillReceiveProps
获取下一次props
值 根据此值 可以做相应的操作
componentWillReceiveProps(nextProps) {
const {
loadStatus: nextLoadStatus,
} = nextProps;
const {
loadStatus,
} = this.props;
const { filterVis, filtered } = this.state;
if (loadStatus === LoadStatus.LOADING && nextLoadStatus === LoadStatus.FINISHED && filterVis) {
this.onChangeSearch(filtered);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Sagasaga
是实现异步Redux
的一个中间件 负责传递ajax
请求 和 根据进度返回相应的loadStatus
、相应的返回值
import baseApi from './baseApi';
export default {
fetchTimeInfo() {
return baseApi.get('/system/time');
},
};
2
3
4
5
6
7
import axios from 'axios';
import { backendUrl } from '../util/constant';
const baseApi = axios.create({
baseURL: backendUrl,
timeout: 30000,
withCredentials: true,
changeOrigin: true,
});
baseApi.interceptors.response.use((response) => {
const { data } = response;
if (typeof data.success === 'boolean' && !data.success) {
return Promise.reject(response);
}
return {
result: data,
receivedAt: Date.now(),
response,
};
}, (err) => {
console.log(err);
if (err.response.status === 302) {
location.href = backendUrl;
window.localStorage.setItem('last', window.location.href);
console.log(`set last to ${window.location.href}`);
}
return Promise.reject(err);
});
export default baseApi;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { call, put, takeLatest } from 'redux-saga/effects';
import types from '../constants/actionTypes';
import layoutApi from '../apis/layoutApi';
import { fetchTimeInfo } from '../actions/layoutAction';
import { timeStamp2String } from '../util/constant';
export function* fetchTimeInfoTask() {
try {
const payload = yield call(layoutApi.fetchTimeInfo);
payload.time = timeStamp2String(payload.result);
yield put(fetchTimeInfo.success(payload));
} catch (err) {
yield put(fetchTimeInfo.failure(err));
}
}
export default function* layoutSaga() {
yield takeLatest(types.FETCH_TIME_INFO_REQUEST, fetchTimeInfoTask);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { all } from 'redux-saga/effects';
import layoutSaga from './layoutSaga';
export default function* rootSaga() {
yield all([
layoutSaga(),
]);
}
2
3
4
5
6
7
8
import { applyMiddleware, createStore, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import thunkMiddleware from 'redux-thunk';
import rootReducer from '../reducers';
import rootSaga from '../sagas';
import DevTools from '../containers/DevTools';
export default function configureStore(initialState) {
const sagaMiddleware = createSagaMiddleware();
const middlewares = [
thunkMiddleware,
sagaMiddleware,
];
console.log(`NODE_ENV: ${process.env.NODE_ENV}`);
let store = {};
if (process.env.NODE_ENV === 'production') {
store = createStore(rootReducer, initialState, compose(applyMiddleware(...middlewares)));
} else {
store = createStore(rootReducer, initialState, compose(
applyMiddleware(...middlewares),
DevTools.instrument(),
));
}
sagaMiddleware.run(rootSaga);
return store;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Saga
可以实现ajax
请求统一管理 通过loadStatus
来判断api调用状态
通过选择takeLeading/takeLatest/takeEvery
来管理ajax
请求 避免堆积pending
数据流redux
的生命周期 从调用dispatch
开始
当组件开始渲染 调用了 某个action
函数 可能是写在componentDidMount
亦或是 普通函数中的 this.props.xxxx()
;
之后 调用mapDispatchToProps
通过withRouter
传递给action
函数
action
拿到请求之后 返回相应的type
之后reducer
执行相应的更新store
相对而言 异步调用action
会分两步走
一开始的type
是XXXX.REQUEST
action
之后调用一次reducer
之后执行一次api
当收到返回值之后 调用saga函数
saga
函数会自动返回一个type XXXX.SUCCESS
如果超时未收到返回则 action
返回 type XXXX.FAILURED
之后第二次reducer
此外Rudux
自带非常友好的调试机制 通过contr + q 调用 contr + h切换
————————————————————————
PS:参考:
updated 4/23/2018