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.REQUESTaction之后调用一次reducer
之后执行一次api
当收到返回值之后 调用saga函数
saga函数会自动返回一个type XXXX.SUCCESS
如果超时未收到返回则 action 返回 type XXXX.FAILURED
之后第二次reducer
此外Rudux 自带非常友好的调试机制 通过contr + q 调用 contr + h切换
————————————————————————
PS:参考:
updated 4/23/2018