状态管理工具解决的问题:管理被多个组件所依赖或影响的状态
-
异步方案
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 typesconst GET_DATA = 'GET_DATA';//action creatorconst getData = function(id) {return {type: GET_DATA,payload: api.getData(id) //payload为promise对象}}//reducerfunction 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 stateselectedPostsSelector // 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```javascriptimport { 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 inlineACTION_ONE: (key, value) => ({ [key]: value }),// array formACTION_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性能调优
