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,
]);
import { createConstants } from '../util/utils';
export default createConstants(
'CHANGE_COLLAPSE',
'FETCH_TIME_INFO_REQUEST',
'FETCH_TIME_INFO_SUCCESS',
'FETCH_TIME_INFO_FAILURE',
}
const LoadStatus = {
CACHED: 'cached',
CHECKING: 'checking',
LOADING: 'loading',
UPDATING: 'updating',
PROCESSING: 'processing',
FINISHED: 'finished',
ERROR: 'error',
NONE: 'none',
};
export default LoadStatus;
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;
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import layoutReducer from './layoutReducer';
const rootReducer = combineReducers({
layout: layoutReducer,
});
export default rootReducer;
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));
当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);
}
}
Sagasaga
是实现异步Redux
的一个中间件 负责传递ajax
请求 和 根据进度返回相应的loadStatus
、相应的返回值
import baseApi from './baseApi';
export default {
fetchTimeInfo() {
return baseApi.get('/system/time');
},
};
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;
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);
import { all } from 'redux-saga/effects';
import layoutSaga from './layoutSaga';
export default function* rootSaga() {
yield all([
layoutSaga(),
]);
}
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;
}
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