状态管理工具解决的问题:管理被多个组件所依赖或影响的状态
-
异步方案
redux-thunk:异步控制中间件
- redux-promise:让 action 或 action 的 payload 支持异步
- redux-saga:让异步行为成为架构中独立的一层(saga)
redux-thunk
让 dispath 可以支持函数类型,在这之前只能是纯对象(plain object) ```javascript import { createStore, applyMiddleware } from ‘redux’; import thunk from ‘redux-thunk’; import rootReducer from ‘./reducers’;
// createStore的时候传入thunk中间件 const store = createStore(rootReducer, applyMiddleware(thunk));
// 发起网络请求的方法 function fetchSecretSauce() { return fetch(‘https://www.baidu.com/s?wd=Secret%20Sauce‘); }
// 下面两个是普通的action function makeASandwich(forPerson, secretSauce) { return { type: ‘MAKE_SANDWICH’, forPerson, secretSauce, }; }
function apologize(fromPerson, toPerson, error) { return { type: ‘APOLOGIZE’, fromPerson, toPerson, error, }; }
// 这是一个异步action,先请求网络,成功就makeASandwich,失败就apologize function makeASandwichWithSecretSauce(forPerson) { return function (dispatch) { return fetchSecretSauce().then( (sauce) => dispatch(makeASandwich(forPerson, sauce)), (error) => dispatch(apologize(‘The Sandwich Shop’, forPerson, error)), ); }; }
// 最终dispatch的是异步action makeASandwichWithSecretSauce store.dispatch(makeASandwichWithSecretSauce(‘Me’));
缺点:写起来代码过于冗余
<a name="R7UxF"></a>
## redux-promise
当 action 的 payload 属性为 Promise 对象时,触发一个此 action 的拷贝,payload 为 promise 的执行结果
- resolve:设置 action 的 status 属性为 "success"
- reject:设置 action 的 status 属性为 "error"
```javascript
//action types
const GET_DATA = 'GET_DATA';
//action creator
const getData = function(id) {
return {
type: GET_DATA,
payload: api.getData(id) //payload为promise对象
}
}
//reducer
function reducer(oldState, action) {
switch(action.type) {
case GET_DATA:
if (action.status === 'success') {
return successState
} else {
return errorState
}
}
}
redux-saga
redux-saga 成为 component 及 reducer 之间沟通的桥梁
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from '../reducer';
import mySaga from '../saga';
const sagaMiddleware = createSagaMiddleware();
export default createStore(reducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(mySaga);
import { call, put, takeEvery } from 'redux-saga/effects';
const FETCH_DATA = 'FETCH_DATA';
const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS';
function* fetchData() {
const data = yield call(
() => fetch('https://httpbin.org/get')
.then(response => response.json()),
);
yield put({ type: FETCH_DATA_SUCCESS, payload: { data } });
}
function* mySaga() {
yield takeEvery(FETCH_DATA, fetchData);
}
export default mySaga;
export const FETCH_DATA = 'FETCH_DATA';
export const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS';
const initState = {
data: {},
};
const reducer = (state = initState, action) => {
switch (action.type) {
case FETCH_DATA_SUCCESS:
return {
...state,
data: action.payload.data,
};
default:
return { ...state };
}
};
export default reducer;
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { FETCH_DATA } from '../reducer';
const Main = (props) => {
const { data } = props;
useEffect(() => {
props.getData();
}, []);
return (
<div>
{JSON.stringify(data)}
</div>
);
};
const mapStateToProps = state => ({
data: state.data,
});
const mapDispatchToProps = dispatch => ({
getData: () => { dispatch({ type: FETCH_DATA }); },
});
export default connect(mapStateToProps, mapDispatchToProps)(Main);
性能优化
- reselect:缓存状态,减少 redux 不必要的刷新,提高性能
reselect
设计初衷
为了解决 react-redux 架构中,在把全局 store 里的 state 树的部分状态映射到对应组件的 props 中(mapStateToProps),每次组件更新的时候都需要执行计算的问题。即便组件依赖的状态没有改变。
使用
reselect 提供的 createSelector 方法主要是替代 mapStateToProps 函数的,并提供了缓存和比较机制,来减少组件更新时的无谓计算,从而提高性能。
- reselect 默认只有一份 cache,cache 命中的策略是浅比较,引用改变了会导致 cache 失效 ```javascript // Reselect selector // Takes a list of posts and post Ids, and picks out // the selected Posts import { createSelector } from ‘reselect’; import _ from ‘loadash’;
// Create select functions to pick off the pieces of state we care about // for this calculation // 采用 reselect 后,相当于记忆缓存,会缓存状态 // 如果 state.posts 和 state.selectedPostIds 发生变化,它会重新计算 state // 但是发生在其他部分的 state 变化,就不会重新计算 const postsSelector = state => state.posts; const selectedPostsSelector = state => state.selectedPostIds;
// 参数名字无所谓 const getPosts = (posts, selectedPostIds) => { const selectPosts = .filter( posts, post => .contains(selectedPostIds, post.id) );
return selectPosts;
}
/**
- 前面几个参数:state selecting functions,当 state 改变时,这些函数都会被执行
- 前面这些参数产生的结果都会传到最后一个函数
*/
const SelectedPostsSelector = createSelector([
], getPosts // last argument is the function that has our select logic );postsSelector, // pick off a piece of state
selectedPostsSelector // pick off a piece of state
// App const mapStateToPorps = state => ({ posts: SelectedPostsSelector(state) });
export default connect(mapStateToPorps)(App);
<a name="MHnh5"></a>
# 优化写法
- redux-actions:简化接口调用
<a name="iCW8K"></a>
## redux-actions
> 一个帮助 redux 应用快速创建 actions、reducers 的辅助类函数库
<a name="ggtGc"></a>
### createAction
```javascript
import { createAction } from 'redux-actions';
const increment = createAction('increment');
let incrementObj = increment();// { type:"increment"}
let objincrement = increment(10);// {type:"increment",paylaod:10}
const increment = createAction('increment',(t)=> t * 2);
let objincrement = increment(10);// {type:"increment",paylaod:20}
原理实现:其实就是个柯里化函数
function createAction(type,payloadCreator) {
return (payload) => {
const action = {
type,
};
if(payload !== undefined){
action.payload = payloadCreator?payloadCreator(payload):payload
}
return action;
};
}
createActions
const { actionOne, actionTwo, actionThree } = createActions(
{
// function form; payload creator defined inline
ACTION_ONE: (key, value) => ({ [key]: value }),
// array form
ACTION_TWO: [
first => [first], // payload
(first, second) => ({ second }) // meta
]
// trailing action type string form; payload creator is the identity
},
'ACTION_THREE'
);
expect(actionOne('key', 1)).to.deep.equal({
type: 'ACTION_ONE',
payload: { key: 1 }
});
expect(actionTwo('first', 'second')).to.deep.equal({
type: 'ACTION_TWO',
payload: ['first'],
meta: { second: 'second' }
});
expect(actionThree(3)).to.deep.equal({
type: 'ACTION_THREE',
payload: 3
});
handleActions
const INCREMENT = "increment"
const DECREMENT = "decrement"
var reducer = handleActions({
[INCREMENT]: (state, action) => ({
counter: state.counter + action.payload
}),
[DECREMENT]: (state, action) => ({
counter: state.counter - action.payload
})
},initstate)
//传统的写法
let reducer = (state,action)=>{
switch(action.type){
case "increment":return {count:state.count+1};break;
case "decrement":return {count:state.count-1};break;
default:return state;
}
}
为什么store中不相关的数据的改变会引起界面的重新渲染?
- connect 返回一个监听了 store 改变的 HOC
被 connect 的组件最终是否要 re-render,取决于被 connect 组件被传入的 props 是否改变
- 通过 mapStateToProps、mapDispatchToProps、mergeProps 计算得到最终的 prop ```javascript if (newChildProps === lastChildProps.current) { // 直接通知下层 connect 的组件去执行订阅事件,本组件不触发 re-render if (!renderIsScheduled.current) { notifyNestedSubs() } } else { lastChildProps.current = newChildProps childPropsFromStoreUpdate.current = newChildProps renderIsScheduled.current = true
// 通过 setState 来 re-render forceComponentUpdateDispatch({ type: ‘STORE_UPDATED’, payload: {
error
参考资料
- Understanding Asynchronous Redux Actions with Redux Thunk
- What the heck is a ‘thunk’?
- Redux异步解决方案之Redux-Thunk原理及源码解析
- Redux异步方案总结
- Redux异步方案选型
- redux-saga 实践总结
- 如何使用Reselect做性能优化
- Redux Saga | Redux 界的非同步救星 - 基本用法
- 关于react, redux, react-redux和reselect的一些思考
- reselect源码分析和最佳实践思考
- 使用 Redux 打造你的应用 —— redux-actions
- 推荐使用并手写实现redux-actions原理
- redux-actions 官网
- 深入理解 react/redux 数据流并基于其优化前端性能
- 浅谈 React 数据流管理
- Build Your Own React-Redux Using useReducer and useContext Hooks
- 黑帕云前端性能揭秘:React性能调优